1. Confluence Developer Documentation

Transcription

1. Confluence Developer Documentation
1. Confluence Developer Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1 Confluence Plugin Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.1 Internationalising Confluence Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2 Writing Confluence Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.1 Enabling TinyMCE Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.2 Converting a Plugin to Plugin Framework 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.2.1 Packages available to OSGi plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.3 Creating your Plugin Descriptor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.4 Accessing Confluence Components from Plugin Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.5 Including Javascript and CSS resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.5.1 Plugin Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.6 Adding Plugin and Module Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.7 Adding a Configuration UI for your Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.8 Ensuring Standard Page Decoration in your Plugin UI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.9 Making your Plugin Modules State Aware . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.10 Confluence Plugin Tutorials . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.10.1 Creating A Template Bundle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.10.2 Extending the V2 search API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.10.3 Searching using the V2 Search API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.10.4 Writing a search result renderer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.2.11 Form Token Handling . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3 Confluence Plugin Module Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.1 Code Formatting Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.2 Component Import Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.3 Component Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.4 Component Module - Old Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.5 Decorator Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.6 Editor Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.7 Event Listener Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.7.1 Annotation Based Event Listener Example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.7.2 Writing an Event Listener Plugin Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.8 Extractor Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.8.1 Attachment Content Extractor Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.9 Gadget Plugin Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.10 Job Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.10.1 Workaround pattern for autowiring jobs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.11 Keyboard Shortcut Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.12 Language Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.12.1 Creating A New Confluence Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.12.2 Language Pack Flags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.12.3 Translating ConfluenceActionSupport Content . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.12.4 Translations for the Rich Text Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.12.5 Updating A Confluence Translation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.13 Lifecycle Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.14 Lucene Boosting Strategy Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.15 Macro Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.15.1 Anatomy of a simple but complete macro plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.15.2 Documenting Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.15.3 Including Information in your Macro for the Macro Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.15.4 REV400 Including Information in your Macro for the Macro Browser . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.15.5 WoW Macro explanation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.15.6 Writing Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.16 Module Type Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.17 Path Converter Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.18 Renderer Component Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.19 REST Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.20 RPC Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.21 Servlet Context Listener Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.22 Servlet Context Parameter Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.23 Servlet Filter Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.24 Servlet Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.25 Spring Component Module - Old Style . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.26 Theme Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.26.1 Adding a Theme Icon . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.26.2 Creating a Stylesheet Theme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.26.3 Creating a Theme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.26.4 Packaging and Installing a Theme Plugin . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.26.5 Theme Configuration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.26.6 Updating a theme for editable comments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.27 Trigger Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.28 User Macro Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.29 Velocity Context Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.30 WebDAV Resource Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.31 Web Resource Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.32 Web UI Modules . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.32.1 Web Item Plugin Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.32.2 Web Section Plugin Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.32.3 Web Panel Plugin Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.32.4 Web Panel Renderer Plugin Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.32.5 Web Resource Transformer Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4
6
7
8
10
10
13
13
16
18
20
21
23
24
25
26
26
34
35
36
49
51
53
54
55
57
58
59
61
63
63
66
68
70
70
71
72
74
76
78
79
80
80
81
83
87
88
89
91
93
96
99
101
102
104
105
105
109
110
111
112
114
115
115
116
119
123
126
129
130
131
132
132
135
139
143
146
149
152
154
1.1.3.33 Workflow Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.33.1 Workflow Plugin Prototype . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.34 XWork-WebWork Module . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.1.3.34.1 XWork Plugin Complex Parameters and Security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2 Confluence User Macro Guide . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3 Confluence Remote APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3.1 Confluence REST APIs - Prototype Only . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3.1.1 Using the REST APIs - Prototype Only . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3.2 Confluence XML-RPC and SOAP APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3.3 Remote API Specification for PDF Export . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.3.4 REV400 Confluence XML-RPC and SOAP APIs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4 Development Resources . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.1 Building Confluence From Source Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2 Confluence Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.1 Anti-XSS documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.1.1 Advanced HTML encoding . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.1.2 Enabling XSS Protection in Plugins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.1.3 REV 400 Anti-XSS documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2 Confluence Internals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.1 Bandana Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.2 Confluence Bootstrap Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.3 Confluence Caching Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.4 Confluence Internals History . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.5 Confluence Macro Manager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.6 Confluence Permissions Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.7 Confluence Services . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.8 Confluence UI architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.9 Custom User Directories in Confluence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.10 Date formatting with time zones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.11 HTML to Markup Conversion for the Rich Text Editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.12 HTTP authentication with Seraph . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.13 I18N Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.14 Page Tree API Documentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.15 Password Hash Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.16 Persistence in Confluence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.17 Spring IoC in Confluence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.2.18 Velocity Template Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.3 Confluence UI Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.3.1 Templating in JavaScript with Soy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.4 Deprecation Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.5 DTDs and Schemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.6 Exception Handling Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.7 Generating JSON output in Confluence with Jsonator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.8 Hibernate Sessions and Transaction Management Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.8.1 Hibernate Session and Transaction Management for Bulk Operations . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.9 High Level Architecture Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.10 Javadoc Standards . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.11 Logging Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.12 Migrating to Velocity 1.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.2.13 Spring Usage Guidelines . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3 Confluence Developer FAQ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.1 Disable Velocity Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.2 Enabling Developer Mode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.3 Encrypting error messages in Sybase . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.4 How can I determine the context my macro is being rendered in? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.5 How does RENDERMODE work? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.6 How do I associate my own properties with a ContentEntityObject? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.7 How do I autowire a component? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.8 How do I cache data in a plugin? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.9 How do I check which Jar file a class file belong to? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.10 How do I convert wiki text to HTML? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.11 How do I develop against Confluence with Secure Administrator Sessions? . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.12 How do I display the Confluence System Classpath? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.13 How do I ensure my plugin works properly in a cluster? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.14 How do I find Confluence Performance Tests? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.15 How do I find Confluence Test Suite? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.16 How Do I find enabled and disabled plugins in the Database? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.17 How do I find information about lost attachments? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.18 How do I find the logged in user? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.19 How do I get a reference to a component? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.20 How do I get hold of the GET-Parameters within a Macro? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.21 How do I get hold of the HttpServletRequest? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.22 How do I get my macro output exported to HTML and PDF? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.23 How do I get the base URL and ContextPath of a Confluence installation? . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.24 How do I get the information about Confluence such as version number, build number, build date? . . . . . . . . .
1.4.3.24.1 User Macros . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.25 How do I get the location of the confluence.home directory? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.26 How do I load a resource from a plugin? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.27 How do I make my attachments open in a new window or a tab? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.28 How do I prevent my rendered wiki text from being surrounded by paragraph tags? . . . . . . . . . . . . . . . . . . . . .
156
156
160
162
163
163
163
164
166
178
178
190
190
195
196
200
202
207
209
209
210
212
213
213
216
218
220
222
223
223
227
228
229
232
232
234
236
239
244
245
246
247
247
252
255
257
258
259
261
263
263
264
264
264
264
265
266
266
267
268
268
269
269
269
271
271
271
271
272
272
272
273
273
275
276
277
277
277
277
278
1.4.3.29 How do I tell if a user has permission to...? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.30 How to switch to non-minified Javascript for debugging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.31 how to use Wysiwyg plugin in my new page? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.32 HTTP Response Code Definitions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.33 I am trying to compile a plugin, but get an error about the target release . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.34 REV400 - How do I link to a comment? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.35 Troubleshooting Macros in the Page Gadget . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.36 What's the easiest way to render a velocity template from Java code? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.37 What class should my macro extend? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.38 What class should my XWork action plugin extend? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.39 What is Bandana? One form of Confluence Persistence . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.40 What is the best way to load a class or resource from a plugin? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.3.41 Within a Confluence macro, how do I retrieve the current ContentEntityObject? . . . . . . . . . . . . . . . . . . . . . . . .
1.4.4 Confluence Developer Forum . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.5 Preparing for Confluence 4.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.5.1 Macro Tutorials for Confluence 4.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.5.1.1 Creating a new Confluence 4.0 Macro . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.5.1.2 Extending the macro property panel - an example . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.5.1.3 Preventing XSS issues with macros in Confluence 4.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.5.1.4 Providing an image as a macro placeholder in the editor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.5.1.5 Upgrading and Migrating an Existing Confluence Macro to 4.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.5.2 Plugin Development Upgrade FAQ for 4.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.4.5.3 Plugin points for the editor in 4.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
279
280
281
281
282
283
283
285
285
285
285
286
286
286
287
289
289
294
299
299
301
308
309
Confluence Developer Documentation
Getting Started
There are two main ways to develop with Confluence — using our remote API or developing a plugin. If you are integrating Confluence with
another application, you will most likely want to use the remote API. If you wish to add capabilities to Confluence, a plugin may be the
answer. To get started writing plugins, we recommend that you download the Plugin SDK and follow the instructions to set up a plugin
development environment.
Main Topics
Atlassian Plugin SDK
Get started by setting up your Atlassian plugin development environment.
Themes
Want to customise the look and feel of Confluence? Learn how to provide your custom stylesheets, change layouts and include your own
JavaScript elements into Confluence.
Custom Features
Add new functionality to Confluence by creating your own screens and actions.
Gadgets
Learn how to write Gadgets to expose or consume content in Atlassian applications.
Remote API
Confluence exposes its data via SOAP/XML-RPC and REST services. Learn how to use the remote APIs to integrate Confluence with your
other applications.
Atlassian Development Hubs
Developer Network
Plugin Framework
Gadgets
REST APIs
Confluence
JIRA
GreenHopper
Bamboo
Crowd
FishEye/Crucible
JIRA Mobile Connect
Resources
Javadoc
REST API Prototype
SOAP/RPC Web Service API
DTDs and Schemas Database
Confluence Architecture
Plugin Exchange
Help
Confluence FAQ
Developer FAQ
Atlassian Answers
Atlassian Developer Blog
Atlassian Partners
Plugin Modules
Code Formatting Module
Component Import Module
Component Module
Component Module - Old Style
Decorator Module
Editor Module
Event Listener Module
Extractor Module
Gadget Plugin Module
Job Module
Keyboard Shortcut Module
Language Module
Lifecycle Module
Lucene Boosting Strategy Module
Macro Module
Module Type Module
Path Converter Module
Renderer Component Module
REST Module
RPC Module
Servlet Context Listener Module
Servlet Context Parameter Module
Servlet Filter Module
Servlet Module
Spring Component Module - Old Style
Theme Module
Trigger Module
User Macro Module
Velocity Context Module
WebDAV Resource Module
Web Resource Module
Web UI Modules
Workflow Module
XWork-WebWork Module
Atlassian Developer Blog
The road to HAMS 3.0 - Transaction boundaries
Make your plugin sizzle with new Plugin Exchange enhancements
Testing's In Session
AtlasCamp 2011
Let the Developer Party Begin: AtlasCamp 2011 Save the Date
Confluence Plugin Guide
Confluence's plugin system allows users and developers to customise and extend Confluence.
A plugin is a bundle of code, resources and a special configuration file that can be dropped into a Confluence server to add new functionality,
or change the behaviour of existing features.
Administrators can drop plugins into their Confluence server to add new functionality to the system.
Developers can write plugins for their own Confluence server, or share plugins with other Confluence users.
Some parts of Confluence are implemented entirely as plugins — for example, all macros in Confluence 1.3 and later are written as plugins,
even those included with the system.
Plugins and Plugin Modules
Every plugin is made up of one or more plugin modules. A single plugin may do
many things, while a plugin module represents a single function of the plugin.
For example, a theme plugin will consist of a colour-scheme module to define the
theme's colours, a number of layout modules to define the site's page layouts,
and a theme module to combine those pieces together into a single theme.
Some plugins, such as the macro packs that come with Confluence, are just a
collection of unrelated modules that just happen to be packaged together. Other
plugins, such as theme plugins, have modules that work together to provide
some orchestrated functionality.
Where are plugins stored
Category
Storage
Manually installed
database
Installed via repository
database
Bundled plugins
conf-home
System plugins
WEB-INF/lib
For example, the System plugins chart plugin or the Widget Connector plugin will store data in WEB-INF/lib. Similarly for
advanced-formatting macros.
Where are plugins run-time data stored
There is no distinct requirement where actual plugin's run-time data is stored. It is depended on the particular implementation of each plugin.
The most common storage location would be: database, BANDANA, conf-home or other.
Contents of the Confluence Plugin Guide
Internationalising Confluence Plugins
Writing Confluence Plugins
Enabling TinyMCE Plugins
Converting a Plugin to Plugin Framework 2
Creating your Plugin Descriptor
Accessing Confluence Components from Plugin Modules
Including Javascript and CSS resources
Adding Plugin and Module Resources
Adding a Configuration UI for your Plugin
Ensuring Standard Page Decoration in your Plugin UI
Making your Plugin Modules State Aware
Confluence Plugin Tutorials
Form Token Handling
Confluence Plugin Module Types
Code Formatting Module
Component Import Module
Component Module
Component Module - Old Style
Decorator Module
Editor Module
Event Listener Module
Extractor Module
Gadget Plugin Module
Job Module
Keyboard Shortcut Module
Language Module
Lifecycle Module
Lucene Boosting Strategy Module
Macro Module
Module Type Module
Path Converter Module
Renderer Component Module
REST Module
RPC Module
Servlet Context Listener Module
Servlet Context Parameter Module
Servlet Filter Module
Servlet Module
Spring Component Module - Old Style
Theme Module
Trigger Module
User Macro Module
Velocity Context Module
WebDAV Resource Module
Web Resource Module
Web UI Modules
Workflow Module
XWork-WebWork Module
RELATED TOPICS
Internationalising Confluence Plugins
Atlassian Plugin Exchange
Developing your Plugin using the Atlassian Plugin SDK
Internationalising Confluence Plugins
Text in Confluence plugins can be internationalised to cater for a variety of locales or languages. To do this, you will need to create a
translated copy of the properties file(s) for each plugin and bundle these inside your language pack plugin. Having a properties file in each
plugin allows plugin authors to provide internationalised plugins without having to add their i18n keys to Confluence's core source.
Confluence comes bundled with a few plugins that are stored in a file called atlassian-bundled-plugins.zip. The basic process for
translating a plugin is:
1.
2.
3.
4.
5.
Extract this zip to a directory
Extract the plugin JAR
Locate the properties file which contains i18n keys (examples are below)
Copy this file to the same location in your plugin. For example, if it is in path/to/file.properties, it needs to be in the same place in
your language pack JAR with a locale extension: path/to/file_jp_JP.properties
5. Repeat this for all plugins that can be internationalised
Below is a list of bundled plugins that can be internationalised and the properties file you will need to translate (correct as of Confluence 2.7):
Plugin
Name
Filename
I18N Resources
Usage
Statistics
Plugin
usage-tracking-plugin-<version>.jar
resources/stats/usage.properties
Atlassian
Plugin
Repository
atlassian-plugin-repository-confluence-plugin-<version>.jar
resources/i18n/repository-templates.properties
resources/i18n/repository-macros.properties
Clickr
Theme
clickr-theme-plugin-<version>.jar
clickr.properties
Mail Page
Plugin
mail-page-plugin-<version>.jar
resources/mailpage.properties
Social
Bookmarking
Plugin
socialbookmarking-<version>.jar
com/atlassian/confluence/plugins/socialbookmarking/i18n.properties
WebDAV
Plugin
webdav-plugin-<version>.jar
com/atlassian/confluence/extra/webdav/text.properties
Charting
Plugin
chart-plugin-<version>.jar
chart.properties
TinyMCE
(Rich Text)
Editor
atlassian-tinymce-plugin-<version>.jar
com/atlassian/confluence/extra/tinymceplugin/tinymce.properties
Advanced
Macros
confluence-advanced-macros-<version>.jar
resources/com/atlassian/confluence/plugins/macros/advanced/i18n.properties
Dashboard
Macros
confluence-dashboard-macros-<version>.jar
resources/com/atlassian/confluence/plugins/macros/dashboard/i18n.propertie
Below are the system plugins (found in confluence/WEB-INF/lib/) that can be internationalised and the properties file you will need to
translate:
Plugin Name
Filename
I18N Resources
Information Plugin
confluence-information-plugin-<version>.jar
information.properties
Layout Plugin
confluence-layout-plugin-<version>.jar
layout.properties
Livesearch Plugin
confluence-livesearch-plugin-<version>.jar
livesearch.properties
Dynamic Tasklist Plugin
confluence-dynamictasklist-plugin-<version>.jar
dynamictasklist.properties
Writing Confluence Plugins
Looking for plugins? See the existing plugins and extensions written by the community in the Confluence Extensions
space.
Confluence plugins provide a standard mechanism for extending Confluence. By adding plugins to Confluence you will be able to customise
the site's look and feel, add new macros, event listeners and periodic tasks, and even introduce whole new features.
You can read the Confluence Plugin Guide for an overview of what plugins are. This document introduces Confluence plugins to developers
who may want to write their own.
On this page:
Anatomy of a Plugin
Creating your Plugin Descriptor
Creating a Basic Macro Plugin Skeleton
Confluence Plugin Module Types
Java Classes and Accessing Confluence Components
Adding Plugin and Module Resources
Adding a Configuration UI for your Plugin
Ensuring Standard Page Decoration
Tutorials on Developing Confluence Plugins
Anatomy of a Plugin
A plugin is a single jar file that can be uploaded into Confluence. It consists of
A plugin descriptor
(Optional) Java classes
(Optional) Resources
Plugins are composed of a series of modules, each of which defines a point at which the plugin interfaces with Confluence.
Creating your Plugin Descriptor
The plugin descriptor is a single XML file named atlassian-plugin.xml that tells the application all about the plugin and the modules
contained within it. See Creating your Plugin Descriptor.
Creating a Basic Macro Plugin Skeleton
While even the most basic plugin involves quite a few directories and config files, creating a plugin skeleton is pretty easy and
straightforward. We have prepared a Maven 2 template which does almost all the work for you. Please refer to the documentation in the
Atlassian Developer Network for instructions on setting up your development environment and creating the most basic Confluence macro
plugin. You can use its basic code skeleton to evolve your plugin into one of the categories described below.
Confluence Plugin Module Types
There are plenty of plugin types in Confluence. If you are new to plugin development in Confluence, we strongly suggest you start by writing
a simple Macro Plugin. Macros are easy to write and give you visual feedback at once. Since the default plugin created by the Maven 2
template is a macro too, you can get started in almost no time at all.
Once you know your way around the Confluence API, you can evolve your plugin into something else, or of course create a new plugin and
start from scratch. Each of the following plugin type descriptions assumes you have been able to create the basic plugin skeleton mentioned
in the above paragraph.
See Confluence Plugin Module Types.
Java Classes and Accessing Confluence Components
When you upload a plugin JAR file into Confluence, all the Java classes contained within the JAR are available for your plugin to access. You
can include as many classes as you like, and have them interact with each other. Because Confluence and plugins can export components
for your plugin to use, it's important that you follow the Java package naming conventions to ensure your plugin's classes do not conflict with
Confluence classes or with other plugins.
If you are writing a Java implementation of a plugin module, you will be interested in Accessing Confluence Components from Plugin
Modules.
Adding Plugin and Module Resources
A 'resource' is a non-Java file that a plugin may need in order to operate. See Adding Plugin and Module Resources.
The simplest kind of resource, supported with all plugin module types, is of type download, which makes a resource available for download
from the Confluence server at a particular URL. See Adding Plugin and Module Resources.
Adding a Configuration UI for your Plugin
A plugin for an Atlassian application can specify internal links within the application, to allow the user to configure options for the plugin. This
is useful where your plugin requires configuration or user-specific settings to work. See Adding a Configuration UI for your Plugin.
Ensuring Standard Page Decoration
If you're writing a plugin that is intended for more than one Atlassian application, you can use the standard page decorators supported by
Confluence. This allows your plugin to generate new web pages with consistent decoration by the host application across the Atlassian
products. See Ensuring Standard Page Decoration in your Plugin UI.
Tutorials on Developing Confluence Plugins
If you would like a walkthrough on how to develop specific Confluence plugins, please check out our useful tutorials here: Confluence Plugin
Tutorials
RELATED TOPICS
Enabling TinyMCE Plugins
Converting a Plugin to Plugin Framework 2
Creating your Plugin Descriptor
Accessing Confluence Components from Plugin Modules
Including Javascript and CSS resources
Adding Plugin and Module Resources
Adding a Configuration UI for your Plugin
Ensuring Standard Page Decoration in your Plugin UI
Making your Plugin Modules State Aware
Confluence Plugin Tutorials
Form Token Handling
Confluence Developer FAQ
Developer Network
Enabling TinyMCE Plugins
This documentation refers to a feature in the Confluence 3.1 release cycle. It will not work in Confluence 3.0 or before.
Please check out our Milestone release notes to learn more about our milestone process for Confluence 3.1
TinyMCE is the WYSIWYG editor we use in Confluence. You can now customise and enable TinyMCE plugins in Confluence by converting it
as an Atlassian plugin. You simply need to define a plugin descriptor and provide a small snippet of javascript to configure your plugin.
Please note that this does not mean that all TinyMCE plugins are guaranteed to work in Confluence. Confluence is using a
customised version of TinyMCE, so the plugins may not work 'out of the box' and could require some changes.
Defining the TinyMCE Plugin Resources
You will need to define the TinyMCE plugin resources (e.g. editor_plugin.js) in a [web resource module] with an added context of
'wysiwyg-editor'. Below is an example plugin descriptor for the TinyMCE Search & Replace plugin.
<atlassian-plugin name='TinyMCE Search Replace Plugin' key='tinymce.searchreplace.plugin'>
...
<resource name="searchreplace/" type="download" location="searchreplace/"/>
<web-resource name='TinyMCE Search Replace Web Resources'
key='tinymce-searchreplace-resources'>
<resource name="searchreplace.js" type="download"
location="searchreplace/editor_plugin.js"/>
<resource name="searchreplace-adapter.js" type="download"
location="searchreplace/confluence-adapter.js"/>
<context>wysiwyg-editor</context>
</web-resource>
</atlassian-plugin>
Configuring TinyMCE Plugin Settings
To enable the TinyMCE plugin, you will need to configure the settings object that is typically passed to TinyMCE's init() method. To do this,
simply add some javascript to register your configuration logic with
AJS.Editor.Adapter.addTinyMcePluginInit. The following code enables the search/replace plugin and adds the search and replace
buttons to the second row of the toolbar.
searchreplace-adapter.js
AJS.Editor.Adapter.addTinyMcePluginInit(function(settings) {
settings.plugins += ",searchreplace";
settings.theme_advanced_buttons2 += ",search,replace";
});
Please note that if you are enabling several buttons on the toolbar, the buttons may not appear on pages using the Clickr theme. The theme
has a fixed screen width, hence it is better to display the buttons on the second row of the toolbar.
Converting a Plugin to Plugin Framework 2
Confluence 2.10 and later includes a new plugin framework is based on OSGi and Spring Dynamic Modules. The new plugin framework has
all the advantages of OSGi, plus it will be included into all Atlassian products. This means that writing plugins for different products would be
more consistent and porting the same plugin to different Atlassian applications will require fewer changes than it did in the past. For the full
documentation of the Atlassian Plugin Framework 2, refer to the Plugin Framework Documentation.
Plugins that were written for the old framework will continue to work, but to leverage the new functionality you will need to convert your plugin
to the new framework. This page describes how to migrate an existing plugin to the new Confluence plugin framework.
1. Set your plugin 'plugins-version' flag to version 2
2. Check that packages used by your plugin are available to OSGi plugins
2.1 Rely on automatic package imports
2.2 Customise package imports and exports with a bundle manifest
3. Check that components used by your plugin are available to OSGi plugins
3.1 Specify qualifiers on ambiguous Spring dependencies
3.2 Expose your plugin components to other plugins
3.3 Import components exposed by other plugins
4. Advanced configuration with Spring configuration files
5. Confluence API limitations
1. Set your plugin 'plugins-version' flag to version 2
As described in the documentation there are two types of plugins in the Atlassian Plugin Framework 2:
Version 1 — These may be static (deployed in WEB-INF/lib) or dynamic (via the web UI, only in Confluence)
and should work the same as they did in version 1 of the Atlassian Plugin Framework. The capabilities and features
available to version 1 plugins vary significantly across products.
Version 2 — These plugins are dynamically deployed on an internal OSGi container to provide a consistent set of
features and behaviours, regardless of the application the plugin is running on. Version 2 plugins have to be
specifically declared as such, using the plugins-version="2" attribute in atlassian-plugin.xml.
So the first step of migration is to make your plugin a Version 2 plugin by setting plugins-version="2" attribute in
atlassian-plugin.xml:
<atlassian-plugin name="plugin name" key="plugin key" enabled="true" plugins-version="2">
For the remainder of this document, Version 2 plugin and OSGi plugin should be considered synonymous.
2. Check that packages used by your plugin are available to OSGi plugins
OSGi plugins — plugins with 'plugins-version' set to 2 — are subject to certain restrictions. In particular, an OSGi plugin can access only
those external classes that Confluence (or other plugins) explicitly expose. This means that you can no longer assume that all classes on the
Confluence classpath will be accessible to your plugin.
Refer to the list of packages that Confluence exposes, and ensure that all classes used by your plugin are covered by this list. The list of
packages should be sufficient to access all the functionality of Confluence from within your plugin.
However, many of the third party packages that ship with Confluence are not exported. If your plugin needs any of these libraries, you will
need to package them within the plugin. This has been done to provide better compatibility for plugins if Confluence upgrades those libraries
in the future (eg. API incompatibilities that require code changes). The easiest way to package dependencies with your plugin is to use the
Atlassian Plugin SDK Documentation.
It is very important to ensure that plugin code does not depend on packages that are not exposed, as the problem will only manifest itself
during runtime.
2.1 Rely on automatic package imports
OSGi plugins have their required packages imported transparently. You do not need to do anything to have required packages imported, but
it may help to understand how this works.
Normally, an OSGi bundle needs to explicitly import all packages it needs. To work around this requirement, the plugin framework generates
the list of required packages by scanning the class files inside your plugin and determining which packages they use. Once this list of
packages is determined, the plugin system generates an OSGi manifest and inserts it into your plugin prior to loading it into the OSGi
container. For more information, refer to OSGi Basics and how OSGI bundles are generated.
2.2 Customise package imports and exports with a bundle manifest
If you want to have a full control over what packages are imported by your plugin you can package the plugin as an OSGi bundle. To do this,
you need to specify all necessary entries in a Manifest file inside your plugin JAR file. Using the Bundle Plugin for Maven makes the process
of generating a valid OSGi manifest much simpler.
You might also want to configure a bundle manifest yourself if you want expose a set of packages as being exported from your plugin.
3. Check that components used by your plugin are available to OSGi plugins
The other important restriction for OSGi plugins is that they are only allowed to access those Spring components that are explicitly exposed
by Confluence or other plugins. You can find the list of all components that are available to OSGi plugins under
http://<baseURL>/admin/pluginexports.action. In this list, each Spring component is listed against the interfaces that it provides.
In OSGi, every component must specify the interfaces it provides.
As with the exposed packages, the list of components attempts to cover all Confluence functionality but not to expose all the internals of the
application. If your plugin uses the beans that are not exposed you should be able to find an exposed bean that provides the same
functionality. As with the packages, this list is intentionally limited to try to improve plugin compatibility across releases of Confluence.
It is very important to ensure that plugin code does not depend on beans that are not exposed, as the problem will only manifest itself during
runtime. The easiest way to ensure that there are no dependencies on beans which are not exposed is to use constructor injection. Using
constructor injection will ensure that the plugin fails during the loading if any of the dependencies are not satisfied.
As OSGi plugin components live in their own Spring context separate from Confluence's Spring container, you cannot use
ContainerManager.getComponent() to retrieve your own plugin components (see PLUG-280)
3.1 Specify qualifiers on ambiguous Spring dependencies
In some cases, Confluence exposes more than one bean under the same interface. When this happens, Spring can't determine exactly
which bean to use to satisfy a dependency on that interface. For example, there are two exposed beans that implement the PluginController
interface. Spring will fail to inject the right dependency unless you provide a Spring @Qualifier annotation.
// Confluence has two beans that implement PluginController, so we add a qualifier to specify
which one we want
public void setPluginController(@Qualifier("pluginController") PluginController pluginController)
{
this.pluginController = pluginController;
}
3.2 Expose your plugin components to other plugins
In order to make a component in your plugin available to other plugins you can simply add the public="true" attribute to the component in
your plugin descriptor file. You will need to specify one or more interfaces under which this bean will be exposed.
<component key="pluginScheduler" class="com.atlassian.sal.core.scheduling.TimerPluginScheduler"
public="true" >
<interface>com.atlassian.sal.api.scheduling.PluginScheduler</interface>
</component>
3.3 Import components exposed by other plugins
Components that are exposed by other plugins are treated a little differently to beans that are exposed by Confluence itself. Your plugin
needs to specifically import components which come from other plugins. To do this, include a <component-import> tag inside
atlassian-plugin.xml file.
<component-import key="loc" interface="com.atlassian.sal.api.license.LicenseHandler" />
You will also need to ensure that the component class is imported, which usually happens transparently.
4. Advanced configuration with Spring configuration files
The new plugin framework provides the ability to create plugin components using complete Spring configuration files. If you provide Spring
Dynamic Modules (Spring DM) configuration files in META-INF/spring/, these will be loaded into your plugin OSGi bundle by the Spring DM
loader. Using this option for configuration provides you with a lot of flexibility about how your plugin components are created and managed.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
xmlns:osgi="http://www.springframework.org/schema/osgi"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/osgi
http://www.springframework.org/schema/osgi/spring-osgi.xsd"
default-autowire="autodetect">
<beans:bean id="repositoryManager"
class="com.atlassian.plugin.repository.logic.ConfluenceRepositoryManager"/>
...
</beans:beans>
To include a Spring configuration file, ensure it is included in the META-INF/spring/ directory inside your plugin JAR file. All files matching
*.xml inside this directory will be loaded as Spring configuration files when your plugin is loaded. For more details on Spring configuration,
see the Spring documentation.
5. Confluence API limitations
As mentioned above, you cannot use ContainerManager.getComponent() to retrieve your own plugin components. Instead,
you should use dependency injection.
VelocityUtil.getRenderedTemplate() uses Confluence's Class loader. Therefore, you cannot use it to access your plugin's
templates. See CONF-14459 for a workaround.
RELATED TOPICS
Writing Confluence Plugins
Enabling TinyMCE Plugins
Converting a Plugin to Plugin Framework 2
Creating your Plugin Descriptor
Accessing Confluence Components from Plugin Modules
Including Javascript and CSS resources
Adding Plugin and Module Resources
Adding a Configuration UI for your Plugin
Ensuring Standard Page Decoration in your Plugin UI
Making your Plugin Modules State Aware
Confluence Plugin Tutorials
Form Token Handling
Plugin Framework Developer Guide
Packages available to OSGi plugins
Below are the Java packages exposed by Confluence. All of them, along with their sub-packages, are available to OSGi plugins running in
the Atlassian Plugin Framework.
com.atlassian*
com.sun*
com.thoughtworks.xstream*
bucket*
net.sf.cglib*
net.sf.hibernate*
com.opensymphony.*
org.apache*
org.xml.*
javax.*
org.w3c.*
org.dom4j*
org.quartz*
org.bouncycastle*
Inside the application, this list is configured as a parameter to the packageScanningConfiguration component in the
pluginServiceContext.xml file. The XML file is in the services folder within the
confluence/WEB-INF/lib/confluence-x.x.x.jar.
RELATED TOPICS
Converting a Plugin to Plugin Framework 2
Writing Confluence Plugins
Creating your Plugin Descriptor
On this page:
Purpose of a Plugin Descriptor
Example of a Plugin Descriptor
Contents of the Plugin Descriptor
atlassian-plugin element
plugin-info element
description element
version element
application-version element
vendor element
param element
bundle-instructions element
Elements Describing Plugin Modules
Purpose of a Plugin Descriptor
When developing a plugin for an Atlassian application such as Confluence or JIRA, you need to create a 'plugin descriptor' for your plugin.
The plugin descriptor is an XML file that tells the application all about the plugin and the modules contained within it. The descriptor must be
a single file named atlassian-plugin.xml and must be located at the root of the plugin's jar file.
Example of a Plugin Descriptor
Here is a sample plugin descriptor:
Contents of the Plugin Descriptor
Below is a description of the plugin information provided in the descriptor XML file.
atlassian-plugin element
This is the root element for your plugin descriptor. For example, the plugin descriptor file should have this structure:
Attribute
Description
key
Each plugin has a plugin key which must be unique to the plugin. We suggest using the Java convention of reversing
your domain name in order to ensure your key is unique.
The plugin key must be defined in lower case in the plugin descriptor. When you call the plugin via a macro in Wiki
Markup, you can use any capitalisation, e.g. {module1} or {Module1}.
Within the plugin, each module has a module key. Refer to the information on module types for information about the
module key.
name
This is a human-readable name, used for display in menus within the application.
state
To disable the entire plugin by default, specify <atlassian-plugin state="disabled"/>.
plugins-version
To create an OSGi plugin, use plugins-version="2".
The attribute pluginsVersion is still supported but has been deprecated since version 2.1 of the plugin
framework.
plugin-info element
This element contains plugin information displayed by the application for administrators, plugin parameters and OSGi bundle instructions. Its
parent element is <atlassian-plugin>, and it supports several nested elements.
Nested element
Description
<description>
A human-readable description of your plugin.
<version>
The version of your plugin. This number is displayed in the application's plugin manager.
<application-version>
Supply the versions of the application that will support your plugin.
<vendor>
Supply information about the developer of the plugin.
<param>
Supply parameter values if required by your plugin.
<bundle-instructions>
Declare plugin dependencies and shorten your export package lists by specifying OSGi bundle instructions directly
in the plugin XML (OSGi plugins only).
These nested elements are described in more detail below.
description element
The body of this element is a description of your plugin. Its parent element is <plugin-info>.
version element
The body of this element is the current version of your plugin. Its parent element is <plugin-info>.
Plugin versions are sometimes compared within an application to determine the newer version, particularly when performing automated
upgrades. Versions are compared by splitting the version number into components and comparing them numerically first and alphabetically
second.
Following are some sample version numbers in ascending order: 0.99, 1.0, 1.0.1-alpha, 1.0.1-beta, 1.0.1-beta2, 1.0.1, 1.0.1.0, 1.1, 1.2, 1.10,
2.0.
application-version element
Deprecated since Atlassian Plugin Framework 2.2
Describe which versions of the host application are compatible with this plugin. Enforcement of this property varies between applications:
some applications strictly enforce compatibility, while others ignore the value.
Its parent element is <plugin-info>.
Attribute name
Description
min
This is the lowest version of the application which your plugin is compatible with.
max
This is the highest version of the application which your plugin is compatible with.
vendor element
The vendor of the plugin. Provides a link in the plugin administration screens.
Its parent element is <plugin-info>.
Attribute name
Description
name
Supply your name or the name of the company you work for.
url
Supply a web site address.
param element
Arbitrary parameters for a plugin. These can be nested in many other elements. Attribute 'name' gives the name of the parameter. The body
of the element is its value.
Attribute
Description
name
The name of the parameter.
(body)
The value of the parameter.
One commonly used parameter is the URL for your plugin's configuration screen. Its parent element is <plugin-info>. Below is an
example.
bundle-instructions element
This element allows you to declare plugin dependencies and shorten your export package lists by specifying OSGi bundle instructions
directly in the plugin XML. The element's parent element is <plugin-info>.
As seen in the above example, the bundle-instructions element allows child elements, including:
<Export-Package>
<Import-Package>
The Atlassian Plugin Framework uses the bnd tool to generate OSGi bundles. This tool is available as the Bundle Plugin for Maven.
For details of the bnd directives, please refer to the bnd documentation.
Elements Describing Plugin Modules
In the rest of the descriptor XML file, you will define any modules that make up your plugin. Please refer to the list of module types for more
information.
RELATED TOPICS
Writing Confluence Plugins
Enabling TinyMCE Plugins
Converting a Plugin to Plugin Framework 2
Creating your Plugin Descriptor
Accessing Confluence Components from Plugin Modules
Including Javascript and CSS resources
Adding Plugin and Module Resources
Adding a Configuration UI for your Plugin
Ensuring Standard Page Decoration in your Plugin UI
Making your Plugin Modules State Aware
Confluence Plugin Tutorials
Form Token Handling
Information sourced from Plugin Framework documentation
Accessing Confluence Components from Plugin Modules
Confluence is built around Spring, an open-source component framework for Java.
If you are familiar with Spring, then you may only wish to know that Confluence plugin modules (and their implementing classes) are
autowired by name. Thus, if you want to access a Confluence component from your plugin, just include the appropriate setter method in your
implementing class.
If you want to write Confluence plugins but are unfamiliar with Spring, the rest of this page should give you more than enough information on
how to have your plugin interact with Confluence.
Interacting with Confluence
When you are writing anything but the simplest Confluence plugin, you will need to interact with the Confluence application itself in order to
retrieve, change or store information. This document describes how this can be done.
Manager Objects
At the core of Confluence is a group of "Manager" objects. For example, the pageManager is in charge of Confluence pages, the
spaceManager of spaces, the attachmentManager of attachments, and so on.
Dependency Injection
Traditionally, in a component-based system, components are retrieved from some kind of central repository. For example, in an EJB-based
system, you would retrieve the bean from the application server's JNDI repository.
Confluence works the other way round. When a plugin module is instantiated, Confluence determines which components the module needs,
and delivers them to it.
Confluence determines which components a module needs by reflecting on the module's methods. There are two different mechanisms that
are used, based on whether you are using a v1 or v2 plugin.
Setter-based injection (v1 plugins)
With setter-based injection, any method with a signature that matches a standard JavaBeans-style setter of the same name as a Confluence
component will have that component passed to it when the module is initialised.
So, if your plugin module needs to access the pageManager, all you need to do is put the following setter method on your module's
implementing class:
public void setPageManager(PageManager pageManager)
{
this.pageManager = pageManager;
}
Constructor-based injection (v2 plugins)
With constructor-based injection, the constructor which has the greatest number of arguments which can be satisfied by Confluence or plugin
components will be called by Spring when constructing your module.
So, if your plugin module needs to access the pageManager, you need to have a constructor which takes a PageManager instance and
assigns it to a field for later use:
public class MyModule {
private final PageManager pageManager;
public MyModule(PageManager pageManager) {
this.pageManager = pageManager;
}
// ...
}
With constructor injection, you need to make sure there are no circular dependencies among your modules. If there are, you plugin will fail to
start up and you will see an error message in the Confluence log file with information about the dependency problem.
Manager Classes
There are several dozen managers for different areas of functionality in Confluence. The following table lists some of the more commonly
used ones:
Manager class
Responsibility
Sample methods
PageManager
Pages, blogs
getPage(), getBlogPost(), getRecentlyAddedPages(),
findNextBlogPost(), saveContentEntity()
SpaceManager
Spaces
getSpace(), getPersonalSpace(), createSpace()
UserAccessor
Users, groups, preferences
getUser(), addUser(), addMembership(), hasMembership(),
getConfluenceUserPreferences(), getUserProfilePicture()
CommentManager
Comments
getComment(), updateCommentContent()
LabelManager
Labels
addLabel(), removeLabel(), getCurrentContentForLabel()
AttachmentManager
Attachment storage and retrieval
getAttachments(Content), getAttachmentData(Attachment),
saveAttachment()
SmartListManager
Searching (2.8 and earlier)
getListQueryResults()
SearchManager
Searching (2.9 and later)
search(), convertToEntities(), searchEntities()
ContentEntityManager
Saving and retrieving all content. Parent
interface of PageManager, CommentManager,
etc.
saveContentEntity(), getVersionHistorySummaries()
SettingsManager
Global, space, plugin configuration
getGlobalSettings(), updateSpaceSettings(),
getPluginSettings()
I18NBean
Getting localised text
getText(String), getText(String, Object[]), getText(String,
List)
PermissionManager
Checking permissions (do this before calling a
manager)
hasPermission(), hasCreatePermission(),
isConfluenceAdministrator(), getPermittedEntities()
SpacePermissionManager
Adding or modifying space permissions
savePermission(), getGlobalPermissions(),
getGroupsWithPermissions()
EventManager
Register listeners or publish events
publishEvent(), registerListener()
WebInterfaceManager
Rendering web-sections and web-items in
Velocity
getDisplayableSections(), getDisplayableItems()
Note that these are all interfaces. The actual implementation will be injected in your class by Spring, if you include the appropriate setter
method in your class as described above.
Do not directly use implementations or cast the injected class to a particular implementation. Implementation classes are subject to change
across versions without warning. Where possible, interface methods will be marked as deprecated for two major versions before being
removed.
Service Classes
Managers in Confluence are responsible for the data integrity of their domain, but they are not generally responsible for validation or security.
Invalid calls will typically result in a runtime exception. Historically, this wasn't a major problem, but as time went by there was more
duplication of this functionality across actions, remote API methods and plugins. In recent releases, a service layer is being introduced in
Confluence to address this.
The services will follow a command pattern, where the service is responsible for creating a command that can then be validated and
executed. The following nascent services are available:
Service class
Responsibility
Sample commands
CommentService
Comments
CreateCommentCommand, DeleteCommentCommand, EditCommentCommand
PageService
Pages, blog posts
MovePageCommand
SearchService (2.9+)
Performing searches
These simpler services don't follow the command pattern, nor do they perform any data modification. They are generally used to simplify
other functionality:
Service class
Responsibility
HttpRetrievalService
Http Connectivity to External Services
More information
Spring IoC in Confluence, a longer guide to Spring in Confluence.
Confluence API documentation, which include the bean interfaces and classes.
Confluence Developer FAQ
Including Javascript and CSS resources
Available:
Confluence 2.10 and later
Deprecated:
DWR was deprecated in Confluence 3.3
Good style for web applications requires that JavaScript and CSS for web pages are kept separate to the HTML they enhance. Confluence
itself is moving towards this model, and the tools that Confluence uses to do this are also available to plugin developers.
If you are developing a theme plugin and would like to include css resources, see Theme Stylesheets instead.
Including a Custom JavaScript or CSS File from a Plugin
In your atlassian-plugin.xml, you should add a Web Resource module. See Web Resource Module.
For each resource, the location of the resource should match the path to the resource in your plugin JAR file. Resource paths are
namespaced to your plugin, so they can't conflict with resources in other plugins with the same location (unlike say i18n or Velocity
resources). However, you may find it convenient to use a path name which is specific to your plugin to be consistent with these other types.
To include your custom web resource in a page where your plugin is used, you use the #requireResource Velocity macro like this:
#requireResource("com.acme.example.plugin:web-resource-key")
Where "com.acme.example.plugin:web-resource-key" should be your plugin key, a colon, and the key of the web resource module
in your plugin.
Only one instance of each script or stylesheet will be included, and they will appear in the order they are requested in the Velocity rendering
process.
The rich text editor does not currently use dynamic stylesheets provided by a macro rendered in this way.
Web Resource Configuration
Within your Web Resource plugin module, you will define one or more resource definitions. See Adding Plugin and Module Resources.
Note that you can declare the media type (for CSS resources) and whether the resource should be wrapped in an Internet Explorer
conditional comment. This feature is also described in Adding Plugin and Module Resources.
Here is a short example:
<web-resource key="my-macro-resources">
<resource type="download" name="macro.js" location="path/inside/jar/to/js/macro.js"/>
<resource type="download" name="more-macro-stuff.js"
location="path/inside/jar/to/js/more-macro-stuff.js"/>
<resource type="download" name="macro.css" location="path/inside/jar/to/css/macro.css"/>
<resource type="download" name="macro-ie.css" location="path/inside/jar/to/css/macro-ie.css">
<param name="ieonly" value="true"/>
<param name="title" value="IE styles for My Awesome Macro"/>
</resource>
<resource type="download" name="macro-print.css"
location="path/inside/jar/to/css/macro-print.css">
<param name="media" value="print"/>
</resource>
<dependency>confluence.web.resources:ajs</dependency> <!-- depends on jQuery/AJS -->
</web-resource>
See below for the libraries provided by Confluence which you can include as a dependency.
Resource dependencies are not supported in 2.10. You will need to define the depending resources explicitly
Including a JavaScript Library provided by Confluence
Confluence currently includes several JavaScript libraries which plugins can use. The versions of these libraries are subject to change, but
only across major versions of Confluence.
In the Confluence source code, these libraries are included in a plugin XML file called web-resources.xml.
Library
Web resource key
Confluence
3.2
Confluence
3.3+
Details
jQuery +
AJS
confluence.web.resources:ajs
1.2.6
1.4.2
Atlassian's JS abstraction on top of jQuery provides a
few additional pieces of functionality.
jQuery
confluence.web.resources:jquery
1.2.6
1.4.2
For compatibility with prototype, you must use
'jQuery()' not '$' to access jQuery.
To include one of these libraries in all pages in which your Velocity template appear, simply use the #requireResource macro as above.
For example, if your macro requires jQuery, add the following to its Velocity template:
#requireResource("confluence.web.resources:jquery")
Deprecated libraries
Use of Scriptaculous, Prototype and DWR is deprecated. Use of these libraries in Confluence core will be gradually replaced with jQuery over
the next few releases. Plugin developers should start doing the same with their front-end code, because these libraries will at some point, be
removed in a future release of Confluence.
The 'Prototype' and 'Scriptaculous' libraries will no longer be available in Confluence 3.5.
DWR has been deprecated as of 3.3. Support for the client side Javascript proxies has been moved into the Confluence
Legacy Web Resources plugin. This plugin is disabled by default. If you need any of the following web resources you have
to enable this plugin:
DWR framework
DWR Javascript proxies for label (add, remove, suggest) or editor operations (heartbeat, draft saving, editor
preferences)
Library
Web resource key
Current
version
Details
Scriptaculous
confluence.web.resources:scriptaculous
1.5rc3
Deprecated. Do not use. Includes effects, dragdrop,
controls and util.
Prototype
confluence.web.resources:prototype
1.4.0_pre11
Deprecated. Do not use. Version found in the
scriptaculous lib directory. Also includes a domready
extension, see domready.js.
DWR
confluence.web.resources:dwr
1.1.4
Deprecated. Do not use. Includes engine and util.
Running Scripts when the Page Loads
The recommended way to load scripts when the page is ready, known as 'on-DOM-ready', is to use the Atlassian JavaScript (AJS)
abstraction. This avoids depending on a particular JavaScript library which may not remain in Confluence.
AJS.toInit(function () {
// ... your initialisation code here
});
This has the additional benefit of ensuring any functions or variables you declare here are not in the global scope, which is important for best
interoperability with other plugins in Confluence.
Achieving Progressive Enhancement
We recommend you separate your markup, styles and JavaScript when developing a Confluence plugin, according to the design principles of
progressive enhancement. To assist with this, there are a few hooks in AJS and in Confluence in general to make this easier.
Dynamic Content in JavaScript
If you need to pass information from Velocity to JavaScript, such as for localised text, you can use AJS.params. This automatically looks up
values inside fieldsets marked with a class of "parameters" inside your markup. For example, given the following markup:
<fieldset class="parameters hidden">
<input type="hidden" id="deleteCommentConfirmMessage"
value="$action.getText('remove.comment.confirmation.message')">
</fieldset>
You can have your JavaScript access the localised text without embedding it by using AJS.params:
if (confirm(AJS.params.deleteCommentConfirmMessage)) {
// ...
}
Getting the Context Path
Usually, you can use relative paths in stylesheets and JavaScript to avoid the need to know the context path. However, Confluence makes
this available through a meta tag in the header which looks like this:
<meta name="ajs-context-path" content="/confluence">
Since 3.4 the best way of accessing this path is via the AJS.Data JavaScript method:
var relativeUrl = AJS.Data.get("context-path") + "/path/to/content";
More Information
Couldn't you do this already? What's changed in Confluence 2.8?
Since Confluence 2.6, you've been able to use #includeJavascript, which puts the script tag inline, exactly where that Velocity macro
appears. You've also always been able to include inline scripts or styles in your macros. However, there are a couple of problems with this
that we've solved in 2.8:
1. The JavaScript might override other script already present in the page, including scripts used by Confluence.
2. Inline JavaScript or styles might appear multiple times in the page, wasting bandwidth and potentially causing conflicts.
Many plugin authors found that including JavaScript in their plugins meant the plugin broke in some places, such as in the preview window, if
two copies of the macro were on the same page.
By using the new #requireResource, you're guaranteed to get only one instance of the script appearing on a page, and it will be cached
by browsers until your plugin is upgraded.
Do I have to use Velocity to request these resources? What about in Java?
You can achieve the same result in Java via the WebResourceManager. Use the same method as described above for Velocity:
webResourceManager.requireResource(String)
The WebResourceManager is a bean you can get injected by Spring. Do this within the scope of the request.
In most cases, using Velocity makes more sense, because the declaration of the JS and CSS should be close to the code which uses it.
RELATED TOPICS
Web Resource Module
Adding Plugin and Module Resources
Writing Confluence Plugins
Installing a Plugin
Plugin Resources
Defining a Directory of Downloadable Resources
If your plugin requires a lot of resources, you may wish to expose a directory of files as resources, rather than writing definitions for each
individual file.
<resource type="download" name="icons/" location="templates/extra/autofavourite/icons/"/>
The name and location must both have trailing slashes
Subdirectories are also exposed, so in the example above, icons/small/icn_auto_fav.gif will be mapped to the resource
templates/extra/autofavourite/icons/small/icn_auto_fav.gif
Referring to Downloadable Resources
The URL for a downloadable resource is as follows:
{server root}/download/resources/
{plugin key}:{module key}/{resource name}
{module key} is optional.
For example:
http://bamboo.example.com/download/resources/com.atlassian.bamboo.plugin.autofavourite:autofavourite-resources/icn_auto_fav.gif
For details:
Downloadable Plugin Resources
Adding Plugin and Module Resources
Confluence plugins may define downloadable resources. If your plugin requires Confluence to serve additional static files such as images,
Javascript or CSS, you will need to use downloadable plugin resources to make them available.
On this page:
Purpose of a Resource
Example of a Resource Definition
Contents of the Resource Definition
Example of Resource Type: Downloadable Plugin Resources
Example of Resource Type: Stylesheet referring to Images
Values for Param Element
Purpose of a Resource
A 'resource' is a non-Java file that a plugin may need in order to operate. Examples of possible resources might be:
A Velocity file used to generate HTML for a macro or layout plugin module in Confluence.
A CSS file required by a theme layout plugin module.
An image referenced from within a layout plugin module.
A macro help file.
A localisation property file.
Resource definitions can be either a part of the plugin, or part of a particular plugin module.
Example of a Resource Definition
Here is a sample resource definition:
Contents of the Resource Definition
A resource has a name, a type and a location. The resource definition maps an arbitrary resource name to the location of that resource in the
server's classpath.
Element
Attribute
<resource>
<resource>
Description
This block defines the resource. For example: <resource type="velocity" name="template"
location="com/example/plugin/template.vm"/>
name
The name of the resource defines how the plugin module can locate a particular resource. Must be specified if
'namePattern' is not. If your location parameter points to a directory rather than a single resource, you should
specify the name with a trailing '/'. For example: <resource type="download" name="myimages/"
location="com/example/plugin/myimages"/>
Note that for css/javascript resources, they must have the appropriate file extension in the name i.e. .css,
.js
<resource>
namePattern
The pattern to use when loading a directory resource.
<resource>
type
The type of a resource tells the module how that resource can be used. The values allowed are different for
each application.
A module can look for resources of a certain type or name. For example, a layout plugin requires that its
help file is a file of type velocity and name help.
Refer to the examples of resource types below.
<resource>
location
The location of a resource tells the plugin where the resource can be found in the jar file. (Resources are
loaded by Java's classpath resource loader.)
The full path to the file (without a leading slash) is required.
Must end in a '/' when using the 'namePattern' attribute to load multiple resources in a directory.
<property>
key/value
Resources may contain arbitrary key/value pairs. For example: <property key="content-type"
value="text/css"/>
<param>
name/value
Resources may contain arbitrary name/value pairs. For example: <param name="content-type"
value="image/gif"/>. Refer to the list of values for the param element below
Example of Resource Type: Downloadable Plugin Resources
The simplest kind of resource, supported with all plugin module types, is of type download, which makes a resource available for download
from the application at a particular URL.
Example of Resource Type: Stylesheet referring to Images
Stylesheets for your plugin may often refer to images also in your plugin. In which case you would have to make both the stylesheet and
image(s) downloadable.
Note: If you have multiple stylesheets and javascript resources defined, you should put the resource defintions in a Web Resource Module.
To refer to your plugin images in a stylesheet, use a relative path based on the resource name defined for the image (which is 'my-images' in
this case).
my-style.css
To reference images already available in an application, you will need to go up three parent directories like so:
Values for Param Element
These are the common name/value pairs supported by the <param> element.
Name
Value
(Example)
Description
content-type
image/gif
Specify a MIME content type.
media
print
Declare the media type for CSS resources. This is supported by Web Resource plugin modules.
For example, requesting this resource will insert a <link> in the HTML header, with a media value of 'print':
ieonly
true
Specify that the resource should be wrapped in an Internet Explorer conditional comment. This is supported by
Web Resource plugin modules.
For example, the web resource declaration below says that the resource should be wrapped in an Internet
Explorer conditional comment, which means it will only be used by Internet Explorer. This is useful for
IE-specific styling to work around browser bugs.
The HTML output when this resource is included will be something like this:
The ieonly parameter also works for JavaScript resources.
title
(Your title)
RELATED TOPICS
Writing Confluence Plugins
The value given here will form the title attribute of the CSS <link> tag.
Enabling TinyMCE Plugins
Converting a Plugin to Plugin Framework 2
Creating your Plugin Descriptor
Accessing Confluence Components from Plugin Modules
Including Javascript and CSS resources
Adding Plugin and Module Resources
Adding a Configuration UI for your Plugin
Ensuring Standard Page Decoration in your Plugin UI
Making your Plugin Modules State Aware
Confluence Plugin Tutorials
Form Token Handling
Information sourced from Plugin Framework documentation
Adding a Configuration UI for your Plugin
On this page:
Purpose of the Configuration UI
Adding a Configuration Link for the Entire Plugin
Adding a Configuration Link for a Module
Example of a Plugin Configuration UI
Notes
Purpose of the Configuration UI
A plugin for an Atlassian application can specify internal links within the application, to allow the user to configure options for the plugin. This
is useful where your plugin requires configuration or user-specific settings to work.
Here are some examples of plugins which provide a configuration UI:
The Google Maps plugin for Confluence requires a Google API Key from Google, which needs to be configured on each server,
before it will work properly.
The WebDAV plugin for Confluence provides a configuration screen that is available both from the Plugin Manager and from a web
item in the Administration menu.
In Creating your Plugin Descriptor, we tell you how to create the XML descriptor file for your plugin. In Plugin Module Types, we tell you how
to define the modules within your plugin. Below is information on defining the links to the configuration UI for your plugin.
Adding a Configuration Link for the Entire Plugin
To add a configuration link for your plugin as a whole, place a single param element with the name configure.url within the
plugin-info element at the top of the plugin descriptor:
Example of a Plugin Configuration UI
Here is an image showing where the configuration link appear for a plugin within JIRA:
Notes
Configuration links are relative to the application.
The configuration URL is a link to a separate page, which you have defined using one of the following:
A new XWork action that you have defined via an XWork plugin module.
Or a servlet defined via a Servlet plugin module.
Not all host applications support configuration links, so you may need to create a web item link in the administration menu to link to
your configuration page.
RELATED TOPICS
Writing Confluence Plugins
Enabling TinyMCE Plugins
Converting a Plugin to Plugin Framework 2
Creating your Plugin Descriptor
Accessing Confluence Components from Plugin Modules
Including Javascript and CSS resources
Adding Plugin and Module Resources
Adding a Configuration UI for your Plugin
Ensuring Standard Page Decoration in your Plugin UI
Making your Plugin Modules State Aware
Confluence Plugin Tutorials
Form Token Handling
Information sourced from Plugin Framework documentation
Ensuring Standard Page Decoration in your Plugin UI
On this page:
Purpose of the Standard Page Decorators
Specifying a Decorator
Limitations on Standard Page Decoration in Confluence
Purpose of the Standard Page Decorators
Atlassian applications support standard page decorators, allowing your plugin to generate new web pages with consistent decoration by the
host application across the Atlassian products.
Specifying a Decorator
Specify the decorator with an HTML meta tag in your head element:
The following decorators are available.
Decorator
Description
Version of
Atlassian
Plugin
Framework
atl.admin
For application administration pages.
2.1 and later
atl.general
For the header and footer of general pages outside the administration UI.
2.1 and later
atl.popup
For content that you want placed in a new browser popup window.
2.3 and later
atl.userprofile
For content on a page in the user profile.
This decorator will generally be accompanied by a web item link or tab. The tab, if applicable,
should be specified by the tab meta tag. For example:
2.3 and later
In the above example, the value of the content attribute is the ID of the tab. Since plugins can
be shared among applications, we recommend that cross-application plugins define their own tab
to ensure the same ID will be used everywhere.
Note: The profile decorator is still experimental. In some applications it may function in the same
way as atl.general. Tabs are not yet supported by all Atlassian applications. If not supported,
the tab will simply be ignored.
Limitations on Standard Page Decoration in Confluence
In this version of Confluence, the standard page decorators are only available on the following URL patterns:
*.action
*.vm
/display/*
/label/*
Other URLs do not pass through the Sitemesh decoration filter, so the HTML they return will not be decorated.
RELATED TOPICS
Writing Confluence Plugins
Enabling TinyMCE Plugins
Converting a Plugin to Plugin Framework 2
Creating your Plugin Descriptor
Accessing Confluence Components from Plugin Modules
Including Javascript and CSS resources
Adding Plugin and Module Resources
Adding a Configuration UI for your Plugin
Ensuring Standard Page Decoration in your Plugin UI
Making your Plugin Modules State Aware
Confluence Plugin Tutorials
Form Token Handling
Information sourced from Plugin Framework documentation
Making your Plugin Modules State Aware
Description
As a plugin developer, it may be necessary to perform initialisation or shutdown actions when your plugin is enabled or disabled. The
approach required in order to achieve this depends on the type of plugin being developed.
A plugin is a bundle of code, resources and configuration files that can be dropped into an Atlassian product to add new functionality or
change the behaviour of existing features.
Every plugin is made up of one or more plugin modules. A single plugin may do many things, while a plugin module represents a single
function of the plugin.
There are two versions of plugins in the Atlassian Plugin Framework 2:
Version 1 — These may be static (deployed in WEB-INF/lib) or dynamic (via the web UI, only in Confluence) and should work the
same as they did in version 1 of the Atlassian Plugin Framework. The capabilities and features available to version 1 plugins vary
significantly across products.
Version 2 — These plugins are dynamically deployed on an internal OSGi container to provide a consistent set of features and
behaviours, regardless of the application the plugin is running on. Version 2 plugins have to be specifically declared as such, using
the plugins-version="2" attribute in atlassian-plugin.xml.
For Version 1 plugins, the StateAware interface can be implemented by plugin modules which need to know when they are enabled or
disabled.
For Version 2 plugins, Spring lifecycle interfaces can be implemented by Component modules which need to know when they are enabled or
disabled.
Implementation (Version 1 Plugins)
To be notified of enablement/disablement, implement the following in your Macro Module, Event Listener Module or Component Module - Old
Style:
public class YourMacro extends BaseMacro implements com.atlassian.plugin.StateAware
This has two methods you must implement:
public void enabled()
{
// Your enablement code goes here.
}
public void disabled()
{
// Your disablement code goes here.
}
Call Sequence
These methods are called in the following circumstances:
enabled()
1.
2.
3.
4.
At server startup, if the plugin is already installed and enabled.
If the plugin is installed via uploading
If the plugin is enabled after having been disabled.
If the specific module is enabled after having been disabled.
disabled()
1. At server shutdown, if the plugin is installed and enabled.
2. If the plugin is uninstalled.
3.
3. If the plugin is disabled.
4. If the specific module is disabled.
Notes
Each method is only called once at each logical enablement/disablement event. Please note that the module class's constructor is not a
reliable place to put initialisation code either, as the classes are often constructed or destructed more often than they are disabled/enabled.
However, once enabled, the same class will remain in memory until it is disabled.
Supported Module Types
Not all module types have been tested, but the following have the following status:
Module Type
Confluence Version
Macro Module
2.3.3
Component Module - Old Style
2.3.3
Event Listener Module
2.3.3
Lifecycle Module
2.3.3
Working
Implementation (Version 2 Plugins)
The Component Module type for OSGi (version 2) plugins doesn't support the StateAware interface. To achieve the same effect, you can
use the two Spring lifecycle interfaces: InitializingBean and DisposableBean. The afterPropertiesSet() and destroy() methods on these
interfaces will be called when the module is enabled or disabled, exactly like StateAware.
Making this change to a component in an existing plugin will be backwards compatible. That is, a component module in a legacy plugin which
implements InitializingBean will have its init() method called when it is enabled, exactly the same as such a component in an OSGi plugin.
Confluence Plugin Tutorials
Writing a Confluence Theme
Adding a REST Service to Confluence
Writing Macros for Confluence
Writing a Confluence Macro 1
Integration Testing Confluence Plugins – from our friends at Customware
Adding a custom action to Confluence
Adding your own Menu Items to Confluence
Defining a Pluggable Service in a Confluence Plugin
Writing a Confluence macro that uses JSON
Upgrading and Migrating an Existing Confluence Macro to Confluence 4.0
Creating a new Confluence 4.0 Macro
Creating A Template Bundle
Extending the V2 search API
Searching using the V2 Search API
Writing a search result renderer
Creating A Template Bundle
Available:
Confluence 3.2 and later
A template is a pre-defined page that can be used as a prototype when creating new pages. Templates are useful for giving pages a
common style or format. Templates are written in regular Confluence markup, using special markup to define form fields that need to be filled
in. A template bundle is essentially a collection of templates packaged up into a plugin.
The templates framework plugin bundled with Confluence 3.2 allows custom template bundles to be deployed to a Confluence instance by
creating a standard Atlassian Plugin that depends on the templates framework. Once you have created and deployed a custom template
bundle to a Confluence instance, you will be able to import the templates to use globally or within specific spaces.
Before you start
If you are unfamiliar with plugin development, we recommend that you read [DEVNET:How to Build an Atlassian Plugin]
guide before you read this document. You may also want to read Writing Atlassian Plugins for an overview of the plugin
framework. Some experience with Maven is assumed for plugin development. If you haven't used Maven before, you may
want to read the Maven documentation to familiarise yourself with it.
On this page:
Creating a Template Bundle
1. Archetype Your Plugin
2. Define The Dependencies
3. Implement The Interface
4. Install The Template Bundle
Example Code
Creating a Template Bundle
1. Archetype Your Plugin
Create a new plugin as per the Developing your Plugin using the Atlassian Plugin SDK guide. We will define this plugin as a dependency of
the templates framework. The functionality it will provide is an implementation of the TemplatePackage interface which provides a
java.util.List<PageTemplate> to the framework.
Incorrect Confluence version in plugin archetype
If you are not using the latest Atlassian Plugin Development Kit, your plugin archetype may be created with an incorrect
Confluence version. Please check your pom.xml and update the product (Confluence) version to 3.2 or later, or update
your PDK to the latest version and create a new plugin archetype.
The implementation of how your plugins are stored are completely up to you, the example at the end of this page uses an XML file and
JAXB.
2. Define The Dependencies
We have to add both the maven dependency to your pom.xml and a component in the atlassian-plugin.xml file.
Add this dependency to your pom.xml file for the plugin:
<dependency>
<groupId>com.atlassian.confluence.plugins</groupId>
<artifactId>templates-framework</artifactId>
<version>0.4</version>
<scope>provided</scope>
</dependency>
Define this component in your atlassian-plugins.xml file:
<component name="Templates: Default Package" key="templates" public="true"
class="com.atlassian.confluence.plugin.templates.packages.DefaultTemplatesPackage">
<interface>com.atlassian.confluence.plugin.templates.export.TemplatePackage</interface>
</component>
In this example the com.atlassian.confluence.plugin.templates.packages.DefaultTemplatesPackage class is our plugin
implementation, change this to your plugin class. The
<interface>com.atlassian.confluence.plugin.templates.export.TemplatePackage</interface> line should not be
changed as this is the dependency the Atlassian Plugins Framework uses to register your plugin with the templates framework.
3. Implement The Interface
For the plugin to function as a templates bundle, we must implement the TemplatePackage interface that is exported by the templates
framework. This allows the plugin to provide a list of templates to the framework.
The interface defines two methods:
List<PageTemplate> getAvailableTemplates() throws TemplatePackageException;
and
String getPackageName();
The getAvailableTemplates() method will provide the template data to the framework in the form of PageTemplate instances. When
you instantiate these instances you should set the following members of the instance with your template data:
name - not null
content - not null
description
labels
The order of the returned list is not important, as this list will be sorted by the template name before it is rendered.
4. Install The Template Bundle
The template bundle should be installed as a normal plugin, the Atlassian Plugins Framework will take care of registering it with the
templates framework. After it is installed, the template bundle will be available under the Import Templates administration menu item (see
Importing Templates).
Example Code
The example provided here is only applicable to the DefaultTemplatesPackage that is bundled with Confluence 3.2. This plugin stores
the templates as an XML file and uses JAXB to load in the file.
The code samples below are intended to be used as references only, as there are a number of ways that template bundles can be built.
Screenshot: Example directory structure for template bundle
Click to expand any code sample below.
Framework Interface - TemplatePackage.java
package com.atlassian.confluence.plugin.templates.export;
import com.atlassian.confluence.pages.templates.PageTemplate;
import java.util.List;
public interface TemplatePackage {
/**
* Return a collection of the available templates offered by this plugin.
* @return A {@link java.util.List} of {@link
com.atlassian.confluence.pages.templates.PageTemplate}s.
* @throws TemplatePackageException If an exception occurs.
*/
List<PageTemplate> getAvailableTemplates() throws TemplatePackageException;
/**
* Returns the name for this template package
*
* @return The name of this package.
*/
String getPackageName();
}
Example Implementation - DefaultTemplatesPackage.java
package com.atlassian.confluence.plugin.templates.packages;
import
import
import
import
import
com.atlassian.confluence.pages.templates.PageTemplate;
com.atlassian.confluence.plugin.templates.export.TemplatePackage;
com.atlassian.confluence.plugin.templates.export.TemplatePackageException;
org.slf4j.Logger;
org.slf4j.LoggerFactory;
import
import
import
import
import
javax.xml.bind.JAXBContext;
javax.xml.bind.JAXBException;
java.io.InputStream;
java.util.ArrayList;
java.util.List;
public class DefaultTemplatesPackage implements TemplatePackage {
private static final Logger log = LoggerFactory.getLogger(DefaultTemplatesPackage.class);
private static final String TEMPLATES_FILE = "templates.xml";
private static final String NAME = "Default Templates Package";
public DefaultTemplatesPackage() {}
public List<PageTemplate> getAvailableTemplates() throws TemplatePackageException {
Templates templatesXml;
try {
JAXBContext ctx =
JAXBContext.newInstance("com.atlassian.confluence.plugin.templates.packages",
getClass().getClassLoader());
InputStream resourceStream =
getClass().getClassLoader().getResourceAsStream(TEMPLATES_FILE);
templatesXml = (Templates) ctx.createUnmarshaller().unmarshal(resourceStream);
}
catch(JAXBException e) {
throw new TemplatePackageException("Unable to unmarsal xml", e);
}
List<PageTemplate> templates = new ArrayList<PageTemplate>();
for(Templates.Template t : templatesXml.getTemplate()) {
PageTemplate pTemplate = new PageTemplate();
pTemplate.setName(t.getName());
pTemplate.setContent(t.getContent());
pTemplate.setDescription(t.getDescription());
pTemplate.setLabels(t.getLabels());
if(log.isDebugEnabled()) {
log.debug("Loading Template: " + t.getName());
}
templates.add(pTemplate);
}
return templates;
}
public String getPackageName() {
return NAME;
}
}
JAXB XSD Schema
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<xsd:annotation>
<xsd:documentation xml:lang="en">
Schema for the PageTemplate
</xsd:documentation>
</xsd:annotation>
<xsd:element name="templates">
<xsd:complexType>
<xsd:sequence minOccurs="1" maxOccurs="unbounded">
<xsd:element name="template">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="name" type="xsd:string"/>
<xsd:element name="description" type="xsd:string"/>
<xsd:element name="content" type="xsd:string"/>
<xsd:element name="labels" type="xsd:string"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</xsd:schema>
Maven2 changes for XSD generation
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>jaxb2-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>xjc</goal>
</goals>
</execution>
</executions>
<configuration>
<packageName>com.atlassian.confluence.plugin.templates.packages</packageName>
</configuration>
</plugin>
Example templates.xml data
<templates>
<template>
<name>Charts</name>
<description></description>
<content>
<![CDATA[
{tip:title=How to use this template}
* Display tables of data as charts on Confluence pages
* Edit these charts by placing your cursor inside any of the chart macros on this page and click
on the _Insert/Edit Macro_ icon in the toolbar
{tip}
h3. Pie Chart
{chart:title=Estimated Hours Per Feature|xLabel=Time (in
hours)|yLabel=Feature|3D=true|width=500|dataOrientation=vertical}
|| Feature || Hours to complete ||
| autocomplete | 45 |
| bundled themes | 60 |
| JIRA Query Language | 120 |
| Cross-Browser Support | 60 |
{chart}
h3. Bar Chart
{chart:type=bar|title=Estimated Hours Per Feature|xLabel=Time (in
hours)|yLabel=Feature|3D=true|width=500|dataOrientation=vertical}
|| Feature || Hours to complete ||
| autocomplete | 45 |
| bundled themes | 60 |
| JIRA Query Language | 120 |
| Cross-Browser Support | 60 |
{chart}
h3. Line Chart
{chart:type=line|title=Estimated Hours Per Feature|xLabel=Time (in
hours)|yLabel=Feature|width=500|dataOrientation=vertical}
|| Feature || Hours to complete ||
| autocomplete | 45 |
| bundled themes | 60 |
| JIRA Query Language | 120 |
| Cross-Browser Support | 60 |
{chart}
]]>
</content>
<labels></labels>
</template>
<template>
<name>Document List</name>
<description></description>
<content>
<![CDATA[
{tip:title=How to use this template}
* Add an attachment, such as a Word document, to this page and see it displayed below
* It's a great way to share documents and files with other users as they can view the attached
files by clicking the view link.
{tip}
{panel}
h3. Documents
{attachments:upload=true}
\\
{panel}
]]>
</content>
<labels></labels>
</template>
<template>
<name>Meeting Notes</name>
<description></description>
<content>
<![CDATA[
{tip:title=Use Confluence to take notes during your meetings}
Create a new meeting notes page using this template. Keep track of who attended, what has been
discussed during the meeting and what needs to be acted upon.{tip}
h2. Date: {color:#3c78b5}24.4.2009{color}
h2. Attendees
* Joe Black
* John Doe
* &nbsp;
h2. Status of Action Items from Last Week
* Scope out site for offsite (Joe)
* Get three customer testimonials (John)
* &nbsp;
h2. Meeting Agenda
* Review status last week's action items
* Set action items for next week
* &nbsp;
h2. Action Items for Next Week
{tasklist}
Prepare release blog-post
Buy cheese and wine for offsite
{tasklist}
]]>
</content>
<labels></labels>
</template>
</templates>
Extending the V2 search API
If none of the bundled SearchQuery, SearchSort, SearchFilter or ResultFilter implementations fits your requirements, you can
write your own implementation.
Writing your Own SearchQuery
To illustrate how to write your own SearchQuery, we will take a look at how the bundled CreatorQuery was written.
Implement SearchQuery
First of all, you need to implement SearchQuery. This is a generic simple Java object representing your custom search.
public class CreatorQuery implements SearchQuery
{
private static final String KEY = "creator";
private final String creator;
public CreatorQuery(String creator)
{
this.creator = creator;
}
public String getKey()
{
return KEY;
}
public String getCreator()
{
return creator;
}
public List getParameters()
{
return Collections.singletonList(getCreator());
}
}
Comments:
Search query objects should be immutable.
They should be constructed with all the required input to complete the query. In this case, we query on the creator username, so this
is passed as a constructor parameter.
Input should be exposed via an accessor. This will be used by the mapper, which we'll discuss below.
Your query should have a unique key to identify it. This allows us to configure a mapper to map this type of query — more on this
below.
Implement LuceneQueryMapper
The responsibility of the Lucene query mapper is to convert a generic search query POJO (plain old java object) to the actual Lucene search
query.
/**
* Map a CreatorQuery to a Lucene specific query.
*/
public class CreatorQueryMapper implements LuceneQueryMapper<CreatorQuery>
{
public Query convertToLuceneQuery(CreatorQuery creatorQuery)
{
return new TermQuery(new Term(ContentEntityMetadataExtractor.CREATOR_NAME_FIELD,
creatorQuery.getCreator()));
}
}
Comments:
The contract of the LuceneQueryMapper is to return a org.apache.lucene.search.Query given a Confluence v2 search
query object.
We call getCreator() on CreatorQuery and use it to construct the Lucene query.
Add your custom LuceneQueryMapper as a plugin in atlassian-plugins.xml
A new plugin type has been introduced for custom Lucene query mappers using the lucene-query-mapper tag. You should add this to
your plugin descriptor and define what v2 search query objects it can handle.
<atlassian-plugin ...>
...
<lucene-query-mapper key="creator"
class="com.atlassian.confluence.search.v2.lucene.mapper.CreatorQueryMapper" handles="creator"/>
...
</atlassian-plugin>
Comments:
The handles attribute should be set to the unique keys you defined for your custom search query objects (that is,
CreatorQuery.KEY in this example).
If you want to handle multiple query types with your mapper, you can declare this by specifying a <handles>creator</handles>
sub tag for each type supported.
The key attribute is a value to uniquely identify this mapper plugin.
RELATED TOPICS
[Remote API Specification]
API documentation
Searching using the V2 Search API
The v2 search API provides a fast way of searching content within Confluence. We highly recommend that all plugin authors switch to this
API where possible.
To illustrate how to use this API, we have included a simple code snippet for a basic search that:
searches for all content labelled with administration in the space with key DOC.
sorts these results with the latest modified content displayed first.
limits the number of results to 10.
SearchQuery query = BooleanQuery.composeAndQuery(new LabelQuery("administration"), new
InSpaceQuery("DOC"));
SearchSort sort = new ModifiedSort(SearchSort.Order.DESCENDING); // latest modified content first
SearchFilter securityFilter = SiteSearchPermissionsSearchFilter.getInstance();
ResultFilter resultFilter = new SubsetResultFilter(10);
Search search = new Search(query, sort, securityFilter, resultFilter);
SearchResults searchResults;
try
{
searchResults = searchManager.search(search);
}
catch (InvalidSearchException e)
{
// discard search and assign empty results
searchResults = LuceneSearchResults.EMPTY_RESULTS;
}
// iterating over search results
for (SearchResult searchResult : searchResults.getAll())
{
System.out.println("Title: " + searchResult.getDisplayTitle());
System.out.println("Content: " + searchResult.getContent());
System.out.println("SpaceKey: " + searchResult.getSpaceKey());
}
// total number of results found
System.out.println("Total number of results: " + searchResults.getUnfilteredResultsCount());
Further comments:
Please ensure you include
com.atlassian.confluence.search.v2.searchfilter.SiteSearchPermissionsSearchFilter in your search. This is
a bundled filter that will handle permission checking and content filtering automatically for you.
The number of results returned has been limited with the use of
com.atlassian.confluence.search.v2.filter.SubsetResultFilter. This class efficiently filters search results during
search time.
The search is executed using searchManager.search(search). This invocation returns search results populated with data from
your index.
To iterate over the search results returned, you can get a reference to the list of search results with
searchResults.getAll() or an iterator to this list using searchResults.iterator().
Common information about a search result like title, body and space key can be extracted from the search result using
getDisplayTitle(), getContent() and getSpaceKey() respectively. For more accessors see the API
documentation for com.atlassian.confluence.search.v2.SearchResult.
This invocation does not go to the database to construct any search results. If you want
com.atlassian.bonnie.Searchable objects from the database to be returned by the search, call
searchManager.searchEntities(search) instead.
An exception com.atlassian.confluence.search.v2.InvalidSearchException is thrown when either:
there is an error mapping a v2 search object to the corresponding Lucene search object, or
no mapper could be found to map one of the search objects. (The mapper plugin responsible for mapping this search may
have been uninstalled.)
You should simply discard the search if an exception is thrown as described above.
RELATED TOPICS
[Remote API Specification]
API documentation
Writing a search result renderer
Available:
The search results renderer is pluggable in Confluence 3.2 and later
Setting up
With Confluence 3.2 the rendering of search results became pluggable. This means that it is now very easy to add functionality to Confluence
for rendering of site wide searches. This tutorial assumes you are already familiar with how plugins are created and installed into confluence.
If you need to brush up on your knowledge of plugin writing check the following pages:
Setting up your Plugin Development Environment
Developing your Plugin using the Atlassian Plugin SDK
Writing Confluence Plugins
Generate a skeleton
With our basic knowledge in place it is time to actually start writing our plugin. We now have two options for creating our plugin: we can either
use the Maven archetype to create a skeleton or we can set up all the project files manually. For simplicity I chose to use the Maven
archetype as described in Developing your Plugin using the Atlassian Plugin SDK . The instructions on that page will describe in more detail
all the answers I give so please ensure you read it. Now we are ready to get going so I issue the atlas-create-confluence-plugin command.
$atlas-create-confluence-plugin
Executing: /home/dkjellin/src/atlassian-plugin-sdk-3.0.2/apache-maven/bin/mvn
com.atlassian.maven.plugins:maven-confluence-plugin:3.0.2:create
[INFO] Scanning for projects...
[INFO] -----------------------------------------------------------------------[INFO] Building Maven Default Project
[INFO]
task-segment: [com.atlassian.maven.plugins:maven-confluence-plugin:3.0.2:create]
(aggregator-style)
[INFO] -----------------------------------------------------------------------[INFO] [confluence:create]
[INFO] Setting property: classpath.resource.loader.class =>
'org.codehaus.plexus.velocity.ContextClassLoaderResourceLoader'.
[INFO] Setting property: velocimacro.messages.on => 'false'.
[INFO] Setting property: resource.loader => 'classpath'.
[INFO] Setting property: resource.manager.logwhenfound => 'false'.
[INFO] [archetype:generate]
[INFO] Generating project in Interactive mode
[INFO] Archetype repository missing. Using the one from
[com.atlassian.maven.archetypes:confluence-plugin-archetype:RELEASE ->
https://maven.atlassian.com/public] found in catalog internal
Define value for groupId: : com.mycompany.cofluence.plugin.renderer
Define value for artifactId: : thumbnailrenderer
Define value for version: 1.0-SNAPSHOT: :
Define value for package: com.mycompany.cofluence.plugin.renderer: :
Confirm properties configuration:
groupId: com.mycompany.cofluence.plugin.renderer
artifactId: thumbnailrenderer
version: 1.0-SNAPSHOT
package: com.mycompany.cofluence.plugin.renderer
Y: :
[INFO] ---------------------------------------------------------------------------[INFO] Using following parameters for creating OldArchetype: confluence-plugin-archetype:3.0.2
[INFO] ---------------------------------------------------------------------------[INFO] Parameter: groupId, Value: com.mycompany.cofluence.plugin.renderer
[INFO] Parameter: packageName, Value: com.mycompany.cofluence.plugin.renderer
[INFO] Parameter: package, Value: com.mycompany.cofluence.plugin.renderer
[INFO] Parameter: artifactId, Value: thumbnailrenderer
[INFO] Parameter: basedir, Value: /home/dkjellin/tmp/renderer
[INFO] Parameter: version, Value: 1.0-SNAPSHOT
[INFO] ********************* End of debug info from resources from generated POM
***********************
[INFO] OldArchetype created in dir: /home/dkjellin/tmp/renderer/thumbnailrenderer
[INFO] -----------------------------------------------------------------------[INFO] BUILD SUCCESSFUL
[INFO] -----------------------------------------------------------------------[INFO] Total time: 2 minutes 17 seconds
[INFO] Finished at: Fri Dec 11 11:12:33 EST 2009
[INFO] Final Memory: 45M/132M
[INFO] ------------------------------------------------------------------------
For all questions where there is no obvious input, I accepted the default value by pressing enter. Of course there will be minor differences
when you run this on your computer. For example I run Linux but you may run Windows or OSX. As long as we get to "BUILD
SUCCESSFUL" all is good.
What our renderer will do
This tutorial describes how the thumbnail renderer was implemented. As such we will implement the same functionality, therefore first we
must disable the thumbnail renderer so we are sure that it is our code running and not the bundled renderer.
Start
I use Netbeans for development but you can of course use any environment you like. As a sanity check we first compile the skeleton to see
that our environment is set up and working.
$ atlas-compile
This will take some time to execute so be patient. Once the compilation is complete we should try out the skeleton to see that it works. We do
this by issuing
$atlas-run
This will start an instance of Confluence. It will take a few minutes but in the end you should get this:
[WARNING] [talledLocalContainer] INFO: Server startup in 32358 ms
[INFO] [talledLocalContainer] Tomcat 6.x started on port [1990]
[INFO] confluence started successfully and available at http://localhost:1990/confluence
[INFO] Type CTRL-C to exit
Now Confluence is running. We see from the message it is running on port 1990 and under the context 'confluence'. So open the link in a
browser and check it out! The skeleton is not very exciting but going to the plugin page you can see it is installed and enabled:
Do not close Confluence, but rather open a new command prompt for our future work.
Start to do some work
The skeleton includes a macro plugin. You can either remove or edit this file. I suggest removing it and starting 'with a clean slate'. The first
thing we do is to create the Java file needed for a renderer. I then create a new class in the same package called ThumbnailRenderer. This
class needs to implement the SearchResultRenderer interface so I add that to the class declaration right away.
ThumbnailRenderer.java
package com.mycompany.cofluence.plugin.renderer;
import com.atlassian.confluence.plugin.SearchResultRenderer;
import com.atlassian.confluence.search.SearchResultRenderContext;
import com.atlassian.confluence.search.v2.SearchResult;
public class ThumbnailRenderer implements SearchResultRenderer{
public boolean canRender(SearchResult searchResult)
{
return true;
}
public String render(SearchResult searchResult, SearchResultRenderContext renderContext)
{
return "My renderer works!";
}
}
As you can see I added very basic capabilities to this renderer. It will render all results and it will render them all as the string 'My renderer
works!' We should now try this before we move on and make it a bit smarter.
In order to get this renderer in we must modify the atlassian-plugin.xml file created by Maven. This file still contains a reference to our
old macro which is not what we want. Instead we need to declare our class as a spring bean. This allows us to inject dependencies when we
realise we need to do more advanced things. I change the src/main/resources/atlassian-plugin.xml to look like this
atlassian-plugin.xml
<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.artifactId}"
plugins-version="2">
<plugin-info>
<description>${project.description}</description>
<version>${project.version}</version>
<vendor name="${project.organization.name}" url="${project.organization.url}" />
</plugin-info>
<spring key="searchResultThumbnailRenderer" name="Search searcheresult thumbnail renderer"
class="com.mycompany.cofluence.plugin.renderer.ThumbnailRenderer">
<!--All modules can optionally have a description -->
<description>Renders images as thumbnails in search results</description>
</spring>
</atlassian-plugin>
Nothing really strange there. Just a declaration and a key. All this is explained in more detail over at Spring Component Module, but for now
this is all we need. We now want to try it out!
Since Confluence is already running all we need to do is install the plugin. This is really easy and fast! Of course if you have the patience you
can just do ctrl-c and run atlas-compile and atlas-run every time. That works as well, only slower. So in the second command prompt
you opened, type in:
$atlas-cli
This should start the command line interface to Confluence.
[INFO] Waiting for commands...
maven2>
To compile and install the plugin just type:
pi
The SearchResultRenderer was added as part of Confluence 3.2 therefore you can not compile a plugin using an earlier
version of Confluence.
Installing the plugin should take about 2 seconds. Once it is installed, go to your browser. In the top right corner enter 'page' as a search
word and hit enter. You should now be presented with a screen showing that our new renderer is used!
We now know that all the infrastructure is in place so we should now focus on what we want our plugin to actually do.
Only render images
We start by limiting what our plugin should render. We are only interested in images. Everything else should use the default Confluence
renderers. So we look at what we are passed in the canRender method and see what we can do with that. There are several ways to find
the information needed. For example, we could set a breakpoint and stop the code in a debugger. I chose a simpler starting point, simply
writing out the contents of the search result we get. Unfortunately the 'toString' method is not too helpful so we have to manually explode this
to several log statements. To achieve this we also need to add a logger to our class, this is generally a good idea anyway as it gives us a
structured way to output information. If you do write information to the log please carefully read which log level to use first! As the logging we
do now will not be left in the final code I log it all to 'warn' to ensure it appears in the console.
I also changed the renderer to return 'false' for all content. Our canRender method will be called anyway for each result, and by using the
default rendering system I can ensure that I see what content it is. The class now looks like this
Updated renderer
package com.mycompany.cofluence.plugin.renderer;
import com.atlassian.confluence.plugin.SearchResultRenderer;
import com.atlassian.confluence.search.SearchResultRenderContext;
import com.atlassian.confluence.search.v2.SearchResult;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThumbnailRenderer implements SearchResultRenderer{
private static final Logger log = LoggerFactory.getLogger(ThumbnailRenderer.class);
public boolean canRender(SearchResult searchResult)
{
log.warn("\n content: " +searchResult.getContent());
log.warn("\n creator: " +searchResult.getCreator());
log.warn("\n title: " +searchResult.getDisplayTitle());
log.warn("\n modifier: " +searchResult.getLastModifier());
log.warn("\n update desciption: " +searchResult.getLastUpdateDescription());
log.warn("\n space key: " +searchResult.getSpaceKey());
log.warn("\n space name: " +searchResult.getSpaceName());
log.warn("\n type: " +searchResult.getType());
log.warn("\n url: " +searchResult.getUrlPath());
log.warn("\n create date: " +searchResult.getCreationDate().toString());
log.warn("\n extra fields: " +searchResult.getExtraFields().toString());
log.warn("\n handle: " +searchResult.getHandle().toString());
log.warn("\n last modified date: " +searchResult.getLastModificationDate().toString());
return false;
}
public String render(SearchResult searchResult, SearchResultRenderContext renderContext)
{
return "My renderer works!";
}
}
Compile and run it. When Confluence has started search for "png" to find an image. Looking at the search result we see that the last entry is
an image, so we only need to worry about the last log entries. I extracted them below.
INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,405 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] content: null
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,405 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] creator: null
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,406 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] title: InsertLink.png
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,407 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] modifier: null
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,408 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] update desciption: null
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,408 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] space key: ds
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,409 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] space name: Demonstration Space
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,410 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] type: attachment
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,411 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] url:
/pages/viewpageattachments.action?pageId=32789&highlight=InsertLink.png#_Images-attachment-InsertLink.png[INFO]
[talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,412 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] create date: Mon Jul 28 18:01:48 EST 2008
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,412 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] extra fields: {attachmentReadableFileSize=3 kB,
containingContentDisplayTitle=_Images, attachmentTypeDescription=Image,
attachmentMimeType=image/png,
attachmentDownloadPath=/download/attachments/32789/InsertLink.png?version=1&modificationDate=1221536564460,
containingContentId=32789, containingContentUrlPath=/display/ds/_Images}
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,413 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer] handle: com.atlassian.confluence.pages.Attachment-98361
[INFO] [talledLocalContainer] -- referer:
http://localhost:1990/confluence/dosearchsite.action?queryString=jpg&where=conf_all&type=&lastModified=&contributor
| url: /confluence/dosearchsite.action | userName: admin | action: dosearchsite
[INFO] [talledLocalContainer] 2009-12-11 12:14:57,414 WARN [http-1990-3]
[cofluence.plugin.renderer.ThumbnailRenderer] canRender
[INFO] [talledLocalContainer]
[INFO] [talledLocalContainer]
last modified date: Tue Sep 16 13:42:44 EST 2008
-- referer: http://localhost:1990/conflu
Going through this is a bit tedious but it pays off! Look at the field "extra fields". It is a Map<String,String> and it contains a key called
"attachmentMimeType" which is exactly what we are after! So we can now remove all our logging and start doing some smarts around this.
The first step is to get the renderer to only pickup images and leave all other search results alone. When we have fixed that we can move on
to make it render them nice. With that we can update our "canRender" to be like this
Updated canRender method
private static final Set<String> SUPPORTED_MIMETYPES = new HashSet<String>();
static
{
SUPPORTED_MIMETYPES.add("image/png");
SUPPORTED_MIMETYPES.add("image/jpeg");
SUPPORTED_MIMETYPES.add("image/gif");
}
public boolean canRender(SearchResult sr)
{
if (sr.getExtraFields() == null)
{
return false;
}
final String mimeType = sr.getExtraFields().get("attachmentMimeType");
return SUPPORTED_MIMETYPES.contains(StringUtils.defaultString(mimeType).toLowerCase());
}
As you see I decided we should support gif, jpeg and png. I declare them as a static final set as they will not change between invocations,
and therefore only needs to be initialised once. We must ensure that the extraFields are there so we check for null before we move on. We
then get the String out. We make use of org.apache.commons.lang.StringUtils.defaultString as that will provide us with an empty string if we
pass it null, and thus we don't need to check for nulls again. We convert it to lower case just to ensure that we will get matches for all images
regardless of how the mime type was reported.
With these changes in let us try it again and see how it works. As usual we use the "pi" command to install the new version. Again we search
for "png" and we should find some image results. You should now be greeted with a search result like the one below
It all works! Excellent we have now got our canRender method working, so it is time to start thinking about how we want the real results to
look like.
More advanced rendering
We have two options here, we could build the string using something like a stringBuilder and just add things as needed. This would be very
hard to maintain and change in the future. Instead we are going to make use of a velocity file. I have cheated and looked at the default
velocity file used by Confluence and then just modified it with the relevant changes to show the actual thumbnail. It is outside th scope of this
tutorial to cover visual design and also how velocity works. So in order to complete this tutorial I present the finished velocity file here.
thumbnail-search-result.vm
<a href="$req.contextPath$searchResult.extraFields.attachmentDownloadPath"><img src="$thumbUrl"
align="right" hspace="8" height="$thumbHeight"></a>
<h3 class="search-result-title">
<a href="$req.contextPath$searchResult.extraFields.attachmentDownloadPath"
class="$action.contentTypesDisplayMapper.getClassName($searchResult)">$action.getTitleForResult($searchResult)<span
class="icon"></span></a>
</h3>
<fieldset class="hidden">
<input type="hidden" class="search-result-entry-content-id"
value="${searchResult.handle.toString()}">
</fieldset>
<ul class="search-result-metadata">
<li>$!searchResult.extraFields.attachmentReadableFileSize - ${imgWidth}x${imgHeight}</li>
<li><a
href="$req.contextPath$searchResult.extraFields.attachmentDownloadPath">$action.getText('download.name')</a></li></
class="search-result-metadata">
<li><a
href="$req.contextPath/display/$generalUtil.urlEncode($searchResult.spaceKey)">$generalUtil.htmlEncode($searchResul
&gt; <a
href="$req.contextPath$searchResult.extraFields.containingContentUrlPath">$generalUtil.htmlEncode($searchResult.ext
&gt; <a href="$req.contextPath$searchResult.urlPath">$action.getText('type.attachments')</a></li>
<li>$action.dateFormatter.format($searchResult.lastModificationDate)</li>
</ul>
We place this file in the resources folder right next to the atlassian-plugin.xml. If you want your thumbnails rendered in any different way just
change this file, no need to re-compile the sources.
Since this is a runtime dependency any misspellings in the filename will not be picked up until you run the renderer.
Therefore the first time you run it keep an eye on the logs in the console window to ensure that you do not get lots of
exceptions. If you do make sure that you spelled the filename correct in the java file.
We must now update our code to make use of this file for rendering. We will do this in two steps. The first step will be to make the renderer
call this file with just basic data, the second step is to put the customised data in. In our case the customised data is the thumbUrl, imgWidth
and imgHeight.
Step one
For the first step this is what our render method looks like
render method
public String render(SearchResult searchResult, SearchResultRenderContext renderContext)
{
Map context = MacroUtils.defaultVelocityContext();
context.put("searchResult", searchResult);
String result = null;
result =
VelocityUtils.getRenderedTemplate("thumbnail-search-result.vm", context);
return result;
}
It is very airy right now as we will add more code to it soon. We use the MacroUtils to get hold of a defaultVelocityContext for our rendering.
We put the searchResult object into the context under the name searchResult. The we go to the VelocityUtils and ask to get our template
rendered with the current context. Also it looks a bit silly to declare result and assign it null, on the next line we assign it a value and then
return it, bear with me, there is a reason we did it this way. Use the "pi" command to compile and install a new version. You should get a
search result like this one
Not very exciting as we have now managed to get to where we were when we started. Of course we are not done yet. You could now go
ahead and make tweaks to the velocity file and change fonts, colours and other styling elements. I leave that as an excerice for the reader.
What I will do instead is to get the thumbnail in there so over to step two.
Step two
We now know our velocity file loads fine and can render information so it is time to extend functionality. We want to get hold of the thumbnail
for the attached image, this is a bit obscure how this happens but this is the confuence way of doing it. If you remember from our
investigation of the search result it had a "getHandle" method. With this we can get a handle to the attachement. We do this by way of a
DAO. In this case it is one called "AnyTypeDao" this is a spring managed bean. We start by declaring a class variable and a setter (no getter
needed, but it won't hurt to have one). To get Spring to inject this into our renderer we must update our xml declaration to include this. I will
show the complete file shortly, but since I know we must inject two dependencies I will go ahead and prepare the second one now as well.
The second dependency is a ThumbnailManager which will help us do all the thumbnailing work. So we declare this is a class variable as
well giving us a renderer looking like this
Dependency injected renderer
package com.mycompany.cofluence.plugin.renderer;
import com.atlassian.confluence.core.persistence.AnyTypeDao;
import com.atlassian.confluence.pages.thumbnail.ThumbnailManager;
import com.atlassian.confluence.plugin.SearchResultRenderer;
import com.atlassian.confluence.renderer.radeox.macros.MacroUtils;
import com.atlassian.confluence.search.SearchResultRenderContext;
import com.atlassian.confluence.search.v2.SearchResult;
import com.atlassian.confluence.util.velocity.VelocityUtils;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThumbnailRenderer implements SearchResultRenderer{
private
private
private
private
static final Logger log = LoggerFactory.getLogger(ThumbnailRenderer.class);
AnyTypeDao anyTypeDao;
ThumbnailManager thumbnailManager;
static final Set<String> SUPPORTED_MIMETYPES = new HashSet<String>();
static
{
SUPPORTED_MIMETYPES.add("image/png");
SUPPORTED_MIMETYPES.add("image/jpeg");
SUPPORTED_MIMETYPES.add("image/gif");
}
public boolean canRender(SearchResult sr)
{
if (sr.getExtraFields() == null)
{
return false;
}
final String mimeType = sr.getExtraFields().get("attachmentMimeType");
return SUPPORTED_MIMETYPES.contains(StringUtils.defaultString(mimeType).toLowerCase());
}
public String render(SearchResult searchResult, SearchResultRenderContext renderContext)
{
Map context = MacroUtils.defaultVelocityContext();
context.put("searchResult", searchResult);
String result = null;
result =
VelocityUtils.getRenderedTemplate("thumbnail-search-result.vm", context);
return result;
}
/**
* @param anyTypeDao the anyTypeDao to set
*/
public void setAnyTypeDao(AnyTypeDao anyTypeDao)
{
this.anyTypeDao = anyTypeDao;
}
/**
* @param thumbnailManager the thumbnailManager to set
*/
public void setThumbnailManager(ThumbnailManager thumbnailManager)
{
this.thumbnailManager = thumbnailManager;
}
}
Nothing strange here. Let us move over to the atlassian-plugin.xml and declare our need for these files there so they get injected properly.
They are declared using normal Spring xml.
Completed atlassian-plugin.xml
<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.artifactId}"
plugins-version="2">
<plugin-info>
<description>${project.description}</description>
<version>${project.version}</version>
<vendor name="${project.organization.name}" url="${project.organization.url}" />
</plugin-info>
<spring key="searchResultThumbnailRenderer" name="Search searcheresult thumbnail renderer"
class="com.mycompany.cofluence.plugin.renderer.ThumbnailRenderer">
<!--All modules can optionally have a description -->
<description>Renders images as thumbnails in search results</description>
<property name="anyTypeDao">
<ref local="anyTypeDao"/>
</property>
<property name="thumbnailManager">
<ref local="thumbnailManager"/>
</property>
</spring>
</atlassian-plugin>
Now when our renderer is called the dependencies will have been injected and will be ready for use. This gives us the tools we need to
compete this tutorial. We use the anyTypeDao to get hold of an "Attachment" by the handle from the search result. We then use this
attachment to get a ThumbnailInfo object that has a url to our thumbnail as well as the sizes of the original image. If we can not render the
result, say the image was removed by a different user or something else goes wrong we can always return null and Confluence will fall back
on the default renderers provided by Atlassian. The render method should not throw any exceptions, instead catch the exception and return
null. Our finished class now looks like this
Finished ThumbnailRenderer.java
package com.mycompany.cofluence.plugin.renderer;
import com.atlassian.confluence.core.persistence.AnyTypeDao;
import com.atlassian.confluence.pages.Attachment;
import com.atlassian.confluence.pages.thumbnail.CannotGenerateThumbnailException;
import com.atlassian.confluence.pages.thumbnail.ThumbnailInfo;
import com.atlassian.confluence.pages.thumbnail.ThumbnailManager;
import com.atlassian.confluence.plugin.SearchResultRenderer;
import com.atlassian.confluence.renderer.radeox.macros.MacroUtils;
import com.atlassian.confluence.search.SearchResultRenderContext;
import com.atlassian.confluence.search.v2.SearchResult;
import com.atlassian.confluence.util.velocity.VelocityUtils;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThumbnailRenderer implements SearchResultRenderer{
private
private
private
private
static final Logger log = LoggerFactory.getLogger(ThumbnailRenderer.class);
AnyTypeDao anyTypeDao;
ThumbnailManager thumbnailManager;
static final Set<String> SUPPORTED_MIMETYPES = new HashSet<String>();
static
{
SUPPORTED_MIMETYPES.add("image/png");
SUPPORTED_MIMETYPES.add("image/jpeg");
SUPPORTED_MIMETYPES.add("image/gif");
}
public boolean canRender(SearchResult sr)
{
if (sr.getExtraFields() == null)
{
return false;
}
final String mimeType = sr.getExtraFields().get("attachmentMimeType");
return SUPPORTED_MIMETYPES.contains(StringUtils.defaultString(mimeType).toLowerCase());
}
public String render(SearchResult searchResult, SearchResultRenderContext renderContext)
{
Map context = MacroUtils.defaultVelocityContext();
context.put("searchResult", searchResult);
String result = null;
Object o = anyTypeDao.findByHandle(searchResult.getHandle());
if (o instanceof Attachment)
{ //this should always be the case.. but we check just to make sure no exceptions escape.
try
{
ThumbnailInfo info = thumbnailManager.getThumbnailInfo((Attachment) o);
context.put("imgWidth", info.getOriginalWidth());
context.put("imgHeight", info.getOriginalHeight());
context.put("thumbUrl", info.getThumbnailUrlPath());
int thmbheight = info.getThumbnailHeight();
if (thmbheight > 70)
{
thmbheight = 70; //let the browser do the scaling
}
context.put("thumbHeight", thmbheight);
result = VelocityUtils.getRenderedTemplate("thumbnail-search-result.vm",
context);
} catch (CannotGenerateThumbnailException e)
{
log.debug("Exception thrown when generating thumbnail for attachment", e);
}
}
return result;
}
/**
* @param anyTypeDao the anyTypeDao to set
*/
public void setAnyTypeDao(AnyTypeDao anyTypeDao)
{
this.anyTypeDao = anyTypeDao;
}
/**
* @param thumbnailManager the thumbnailManager to set
*/
public void setThumbnailManager(ThumbnailManager thumbnailManager)
{
this.thumbnailManager = thumbnailManager;
}
}
Note there is a limit of 70 pixeles in height. If the image is taller the layout goes a bit funny, so for our renderer we just limit the height to a
maximum of 70 pixels and we don't have to fiddle with CSS or solve this problem. If this was a real renderer we would spend some time to fix
this problem of course. Now save the file and compile and run it. Search for png again and we should see images in our screen!
Mission acomplished!
Conclusion
There, that was not hard was it? Now write your very own renderer for some other type or improve this one to allow for different sized
thumbnails. Maybe add some styling around it can you think of other information that could be relevant? Maybe add EXIF information from
jpeg, there are plenty of options now that you can render the result as you wish. However bear in mind that your canRender and render
methods should perform fast. The canRender will be called ten times for every search, and your render method may be called ten times as
well so it is important that they perform fast.
Form Token Handling
The information on this page is only applicable to Confluence 3.0 and later versions.
Overview and Purpose
Confluence 3.0 employs a new token authentication mechanism that is utilised when Confluence actions are performed either through link
request or form submission. This provides Confluence with the means to validate the origin and intent of the request, thus adding an
additional level of security against cross-site request forgery. While the core Confluence product and its bundled plugins use this token
handling mechanism by default, non-bundled plugins or those developed by third parties may not.
This document is intended for Confluence plugin developers. It provides instructions on how these developers can add this token handling
mechanism to their own plugins. Developers should pay particular attention to the DOC:Timeline section, as unmodified plugins may no
longer function correctly after the cut-off date.
This change affects:
Plugins that provide actions via XWork plugin modules
Plugins that create links to, or submit forms to existing Confluence actions
Form Tokens
Confluence 3.0 requires that WebWork actions possess tokens, which are then verified when the form is submitted back to the Confluence
server.
This is an "opt in" mechanism, whereby actions must declare that they require a token to be present in the request. However, in a future
version of Confluence, the security policy will switch to a more stringent "opt out" system, where actions must declare that they do not require
a token. At this point, any plugin that accepts form submissions and has not been upgraded to use this token authentication mechanism will
cease to function.
Instructions for Plugin Developers
Configuring XWork Actions
There are two mechanisms for providing a Form Token configuration for an XWork action:
Configuration Location
Steps Required
In the Action class
1. Locate the method that is called by the action execution (by default this method is called
execute())
2. Add the @com.atlassian.xwork.RequireSecurityToken annotation to this method:
@ RequireSecurityToken(true) if the method will require a token, or
@ RequireSecurityToken(false) if it will not.
In
atlassian-plugins.xml
1. Locate the action definition (the <action> element in your <xwork> plugin module)
2. Add <param name="RequireSecurityToken">true</param> if you wish the action
execution to require a token, or change its value to false if it does not.
3. ensure that your action uses <interceptor-ref name="validatingStack"/> in its <package> definition
and has an "input" result - which will be used on token failure.
We recommend developers use the atlassian-plugins.xml approach, as it will allow their plugins to be backwards-compatible with
older versions of Confluence.
Providing the token in HTML Forms
The Velocity macro #form_xsrfToken() will insert the following into your form:
<input type="hidden" name="atl_token" value="[the user's token]">
Providing the token in HTML links
The Velocity macro #url_xsrfToken() expands to:
atl_token=[the user's token]
So you can do the following
<a href="myaction.action?activate=true&#url_xsrfToken()">Activate</a>
Providing the token in AJAX calls
The Atlassian Javascript Library (AJS) contains a method that will add the security token to an AJAX callback. In order to make this method
available, you should place the following call in your Velocity template:
#requireResource("confluence.web.resources:safe-ajax")
This library provides wrappers around JQuery AJAX functions that will include the form token in the AJAX submission. If you are not using
the JQuery AJAX functions, you should first update your code to use them directly, then to use the safe version. The following functions are
provided:
AJS.safe.ajax()
AJS.safe.get()
AJS.safe.post()
AJS.safe.getScript()
AJS.safe.getJSON()
Accessing the token programatically
To get hold of the current user's token, you will need to make the following call:
new com.atlassian.xwork.SimpleXsrfTokenGenerator().generateToken(httpServletRequest)
For best long-term compatibility, you should retrieve the name of the form parameter to set from the token generator rather than using the
literal string "atl_token". For example:
HttpServletRequest req = ServletActionContext.getRequest();
if (req != null)
{
XsrfTokenGenerator tokenGenerator = new SimpleXsrfTokenGenerator();
myWebRequest.addParameter(tokenGenerator.getXsrfTokenName(),
tokenGenerator.generateToken(req))
// or: myRequestUrl.append("&" + tokenGenerator.getXsrfTokenName() + "=" +
tokenGenerator.generateToken(req));
}
else
{
// We are not in a web context. Handle this error cleanly.
}
Scripting
Scripts that access Confluence remotely may have trouble acquiring or returning a security token, or maintaining an HTTP session with the
server. There is a way for scripts to opt out of token checking by providing the following HTTP header in the request:
X-Atlassian-Token: no-check
Timeline
Confluence 3.0
Confluence 3.0 will ship with the token generation/checking code in "opt out" mode.
The Future
Our plans are to switch Confluence to ship with a more strict "opt out" protection in the future. At this point, plugins that have not
been modified to use form tokens may cease to function.
We will give more information on these plans once the exact timing is finalised and warn of the changes in advance to give
developers time to test plugin compatibility.
RELATED TOPICS
For more information, refer to the Open Web Application Security Project page.
Confluence Plugin Module Types
Confluence supports the following types of plugin modules:
Module Type
Since
version...
Documentation
Description
codeformatter
2.2
Code Formatting
Module
Adds new languages to the {code} macro
colour-scheme
1.3
Theme Module
A colour-scheme for a theme
component
2.10
Component
Module
Adds components to Confluence's component system. This is the newer and
recommended version of the component module type.
component
1.4
Component
Module - Old
Style
Adds components to Confluence's component system. This is the earlier
version of the component module type.
component-import
2.10
Component
Import Module
Accesses Java components shared by other plugins.
decorator
2.5
Decorator
Module
Adds decorators without using a Theme Plugin
extractor
1.4
Extractor Module
Adds information to the Confluence search index
editor
2.5
Editor Module
Adds a Wysiwyg editor to the Confluence edit page
gadget
3.1
Gadget Plugin
Module
Atlassian gadgets provide a new way to include external content into a
Confluence wiki page.
job
2.2
Job Module
Adds repeatable jobs to Confluence
keyboard-shortcut
3.4
Keyboard
Shortcut Module
defines a keyboard shortcut within Confluence.
language
2.2
Language
Module
Adds language translations to Confluence
layout
1.3
Theme Module
A layout (decorator) definition for a theme
lifecycle
2.3
Lifecycle Module
Schedule tasks to be run on application startup and shutdown
listener
1.4
Event Listener
Module
A component that can respond to events occurring in the Confluence server
lucene-boosting-strategy
3.0
Lucene Boosting
Strategy Module
Tweaks document scores in search results in Confluence.
macro
1.3
Macro Module
A macro used in wiki to HTML conversions (e.g {color}). Outputs HTML that
can be embedded in a page or layout. Can retreive user, page and space info,
or external content (eg RSS)
module-type
2.10
Module Type
Module
Dynamically adds new plugin module types to the plugin framework, generally
building on other plugin modules.
path-converter
2.8
Path Converter
Module
Allows you to install custom URL schemes as a part of your plugin, i.e. you
can have 'pretty' URLs.
rest
3.1
REST Module
Exposes services and data entities as REST APIs.
rpc-soap
1.4
RPC Module
Deploys a SOAP service within Confluence
rpc-xmlrpc
1.4
RPC Module
Deploys an XML-RPC service within Confluence
servlet
1.4
Servlet Module
A standard Java servlet deployed within a Confluence plugin
servlet-context-listener
2.10
Servlet Context
Listener Module
Deploys Java Servlet context listeners as a part of your plugin.
servlet-context-param
2.10
Servlet Context
Parameter
Module
Sets parameters in the Java Servlet context shared by your plugin's servlets,
filters, and listeners.
servlet-filter
2.10
Servlet Filter
Module
Deploys Java Servlet filters as a part of your plugin, specifying the location
and ordering of your filter.
spring
2.2
Spring
Component
Module - Old
Style
Add a Spring component. Unlike component plugins these allow the use of full
Spring configuration XML
theme
1.3
Theme Module
A custom look-and-feel for a Confluence site or space
trigger
2.2
Trigger Module
Adds triggers which schedule jobs
usermacro
2.3
User Macro
Module
Allows a simple macro to be created in the plugin XML file, with no Java
coding necessary
velocity-context-item
1.4
Velocity Context
Module
Adds helper objects to Confluence's Velocity context
web-item
2.2
Web UI Modules
Adds links or tabs to the Confluence UI
web-resource
2.8
Including
Javascript and
CSS resources
Allows you to include Javascript and CSS resources
web-resource-transformer
3.4
Web Resource
Transformer
Module
Web Resource Transformer plugin modules allow you to manipulate static
web resources before they are batched and delivered to the browser
web-section
2.2
Web UI Modules
Adds sections of links to the Confluence UI
xwork
1.4
XWork-WebWork
Module
XWork/Webwork actions and views bunded with a plugin, enabling user
interaction
RELATED TOPICS
Writing Confluence Plugins
Installing a Plugin
Code Formatting Module
Available:
Confluence 2.2 to 3.4.
Deprecated:
As from Confluence 3.5, the code macro does not support custom code highlighting modules.
Code formatting plugin modules allow you to add new languages to the {code} macro. Whenever the code macro is invoked, the macro
checks the 'language' parameter against the languages supported by the available formatting plugins, and uses that plugin to format the
source code.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
Code Formatting Plugins
Here is an example atlassian-plugin.xml file containing a single code formatter:
<atlassian-plugin name="My Formatter" key="confluence.extra.formatters">
...
<codeformatter name="ruby" key="ruby" class="com.example.confluence.formatters.RubyFormatter">
<description>Code formatter for the Ruby programming language</description>
</codeformatter>
...
</atlassian-plugin>
the class attribute defines the class that will be added to the available formatters. This class must implement
com.atlassian.renderer.v2.macro.code.SourceCodeFormatter
The SourceCodeFormatter Interface
All code formatters must implement the following simple interface:
package com.atlassian.renderer.v2.macro.code;
/**
* Strategy for converting a block of source code into pretty-printed HTML. SourceCodeFormatters
MUST be forgiving:
* they will be dealing with user-supplied input, so they can't afford to blow up on bad data.
*/
public interface SourceCodeFormatter
{
/**
* Inform the CodeMacro which languages this formatter supports. So if someone writes
{code:java}, then only
* the formatter that returns "java" from this method will be used to format it.
*
* @return an array of languages that this formatter supports
*/
String[] getSupportedLanguages();
/**
* Convert source code into HTML.
*
* @param code the source code as a string
* @param language the programming language that it is believed this code is written in
* @return the source code formatted as HTML
*/
String format(String code, String language);
}
Formatter Priority
There is no concept of priority for formatters. If two formatters are installed and both return the same value from
getSupportedLanguages(), one will be selected pretty much at random. If you want to avoid this behaviour, deactivate formatters that
you no longer want to use.
Component Import Module
Available:
Confluence 2.10 and later
Purpose of this Module Type
Component Import plugin modules allow you to access Java components shared by other plugins, even if the component is upgraded at
runtime.
Configuration
The root element for the Component Import plugin module is component-import. It allows the following attributes and child elements for
configuration:
Attributes
Name
Required
Description
Default
interface
The Java interface of the component to import. This attribute is only required if the interface elements
are not used.
N/A
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the complete
module key. A module with key fred in a plugin with key com.example.modules will have a complete
key of com.example.modules:fred. I.e. The identifier of the component to import.
filter
The LDAP filter to use to match public components (OSGi services). Note: The format of the filter must be
a valid LDAP filter. (Plugin Framework 2.3 and later.)
Elements
Name
Required
Description
Default
interface
The Java interface under which the component to retrieve is registered. This element can appear zero or
more times, but is required if the interface attribute is not used.
N/A
Example
Here is an example atlassian-plugin.xml file containing a single component import:
It consumes a component made available via a different plugin:
Here is an example of matching via an LDAP filter. Since a component import is really just matching an OSGi service, you can optionally
specify an LDAP filter to match the specific service. Here is an example that matches a dictionary service that provides a language attribute
that equals English:
Notes
Some information to be aware of when developing or configuring a Component Import plugin module:
Component imports, at installation time, are used to generate the atlassian-plugins-spring.xml Spring Framework
configuration file, transforming Component Import plugin modules into OSGi service references using Spring Dynamic Modules.
The imported component will have its bean name set to the component import key, which may be important if using 'by name'
dependency injection.
If you wish to have more control over how imported services are discovered and made available to your plugin, you can create your
own Spring configuration file containing Spring Dynamic Modules elements, stored in META-INF/spring in your plugin jar. This is
recommended if you are needing to import multiple services that implement an interface, for example.
You can use component imports to customise the bean name of host components, particularly useful if you plan to use 'by name'
dependency injection.
RELATED TOPICS
Writing Confluence Plugins
Installing a Plugin
Information sourced from Plugin Framework documentation
Component Module
Available:
Confluence 2.10 and later
Recommended Plugin Module Type
The Component plugin module described below is available to OSGi-based plugins using version 2.x of the Atlassian Plugin Framework,
supported in Confluence 2.10 and later.
We recommend that you use the new plugin module type described below, rather than the old-style Component and Spring Component
module types. Confluence still supports the earlier module types, but the new OSGi-based plugin framework fixes a number of bugs and
limitations experienced by the old-style plugin modules.
Purpose of this Module Type
Component plugin modules enable you to share Java components between other modules in your plugin and optionally with other plugins in
the application.
Configuration
The root element for the Component plugin module is component. It allows the following attributes and child elements for configuration:
Attributes
Name
alias
Required
Description
Default
The alias to use for the component when registering it in the internal bean factory.
The
plugin
key
class
The class which implements this plugin module. The class you need to provide depends on the
module type. For example, Confluence theme, layout and colour-scheme modules can use classes
already provided in Confluence. So you can write a theme-plugin without any Java code. But for
macro and listener modules you need to write your own implementing class and include it in your
plugin. See the plugin framework guide to creating plugin module instances. The Java class of the
component. This does not need to extend or implement any class or interface.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred. I.e. the identifier of the component.
i18n-name-key
The localisation key for the human-readable name of the plugin module.
name
The human-readable name of the plugin module. I.e. the human-readable name of the component.
The
plugin
key.
public
Indicates whether this component should be made available to other plugins via the Component
Import Plugin Module or not.
false
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
Elements
Name
Required
Description
Default
interface
The Java interface under which this component should be registered. This element can appear
zero or more times.
N/A
description
The description of the plugin module. The 'key' attribute can be specified to declare a
localisation key for the value instead of text in the element body.
service-properties
Map of simple properties to associate with a public component (Plugin Framework 2.3 and
later). Child elements are named entry and have key and value attributes.
Example
Here is an example atlassian-plugin.xml file containing a single public component:
Here is an example public component with several service properties:
Notes
Some information to be aware of when developing or configuring a Component plugin module:
Components, at installation time, are used to generate the atlassian-plugins-spring.xml Spring Framework configuration
file, transforming Component plugin modules into Spring bean definitions. The generated file is stored in a temporary plugin jar and
installed into the framework. The plugin author should very rarely need to override this file.
The injection model for components first looks at the constructor with the largest number of arguments and tries to call it, looking up
parameters by type in the plugin's bean factory. If only a no-arg constructor is found, it is called then Spring tries to autowire the
bean by looking at the types used by setter methods. If you wish to have more control over how your components are created and
configured, you can create your own Spring configuration file, stored in META-INF/spring in your plugin jar.
If the public attribute is set to 'true', the component will be turned into an OSGi service under the covers, using Spring Dynamic
Modules to manage its lifecycle.
This module type in non-OSGi (version 1) plugins supported the StateAware interface in some products to allow a component to
react to when it is enabled or disabled. To achieve the same effect, you can use the two Spring lifecycle interfaces: InitializingBean
and DisposableBean. The init() and destroy() methods on these interfaces will be called when the module is enabled or disabled,
exactly like StateAware. Making this change to a component in an existing plugin will be backwards compatible in all but JIRA. That
is, a component module in a legacy plugin which implements InitializingBean will have its init() method called when it is enabled,
exactly the same as such a component in an OSGi plugin.
Components for non-OSGi (version 1) plugins behave very differently to components for OSGi plugins. For version 1 plugins,
components are loaded into the application's object container, be it PicoContainer for JIRA or Spring for all other products that
support components. For OSGi plugins, components are turned into beans for the Spring bean factory for that specific plugin. This
provides more separation for plugins, but means you cannot do things like override JIRA components in OSGi plugins, as you can
for static plugins.
Accessing Your Components
Accessing your components from within other plugin modules is extremely simple. All plugin modules in OSGi plugins are autowired. So to
access a component, you need to add a Java setter method to your plugin module class.
For example, if you wanted to use the above helloWorldService component in an event listener module, you would add a field for the
component and a setter method to the listener class as follows:
public class MyEventListener implements EventListener {
private HelloWorldService helloWorldService;
// ...
public void setHelloWorldService(HelloWorldService helloWorldService) {
this.helloWorldService = helloWorldService;
}
}
Note that to access components in other plugins, the module needs to be marked as 'public' (as covered above) and imported into your
plugin using a component-import.
RELATED TOPICS
Writing Confluence Plugins
Installing a Plugin
Information sourced from Plugin Framework documentation
Component Module - Old Style
Available:
Confluence 2.2 and later
Deprecated:
Confluence 2.10 – use the new Component Module Type instead
This is an outdated module type
The Component plugin module described below belongs to the first version of the Atlassian Plugin Framework. A new
Component plugin module is available to OSGi-based plugins using version 2.x of the Atlassian Plugin Framework,
supported in Confluence 2.10 and later.
Old-Style Plugin Module Type
We recommend that you use the new plugin module type, rather than the old-style Component described below. Confluence still supports the
earlier module type, but the new OSGi-based plugin framework fixes a number of bugs and limitations experienced by the old-style plugin
modules.
Purpose of this Module Type
Component plugin modules enable you to add components to Confluence's internal component system (powered by Spring).
Component plugin modules are available in Confluence 1.4 and later.
Component Plugin Module
Each component module adds a single object to Confluence's component management system.
Other plugins and objects within Confluence can then be autowired with your component. This is very useful for having a single component
that is automatically passed to all of your other plugin modules (ie a Manager object).
Here is an example atlassian-plugin.xml file containing a single component module:
<atlassian-plugin name="Sample Component" key="confluence.extra.component">
...
<component name="Keyed Test Component"
key="testComponent"
alias="bogusComponent"
class="com.atlassian.confluence.plugin.descriptor.BogusComponent" />
...
</atlassian-plugin>
the name attribute represents how this component will be referred to in the interface.
the key attribute represents the internal, system name for your component.
the class attribute represents the class of the component to be created
the alias attribute represents the alias this component will be stored with. This element is optional, if not specified the module key
will be used instead.
Accessing Your Components
Accessing your components is extremely simple.
Autowired Objects
If your object is being autowired (for example another plugin module or an XWork action), the easiest way to access a component is to add a
basic Java setter method.
For example, if you use the above BogusComponent module your object would retrieve the component as follows:
public void setBogusComponent(BogusComponent bogusComponent)
{
this.bogusComponent = bogusComponent;
}
Non-autowired Objects
If your object is not being autowired, you may need to retrieve the component explicitly. This is done via the ContainerManager like so:
BogusComponent bc = (BogusComponent) ContainerManager.getComponent("bogusComponent");
Notes
Some issues to be aware of when developing a component:
One component module can depend on another component module but be careful of circular references (ie A requires B, B requires
A).
The component "namespace" is flat at the moment, so choose a sensible alias for your component.
RELATED TOPICS
Component Module
Writing Confluence Plugins
Installing a Plugin
Decorator Module
Available:
Confluence 2.5 and later
Decorator plugin modules allow you to add decorators without using a Theme Module.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
Decorator Plugin Module
The following is an example atlassian-plugin.xml file containing a single decorator:
<atlassian-plugin key="com.atlassian.confluence.extra.sample" name="Sample Plugin">
...
<decorator name="myDecorator" page="myDecorator.vmd" key="myDecorator">
<description>My sample decorator.</description>
<pattern>/plugins/sampleplugin/*</pattern>
</decorator>
...
</atlassian-plugin>
the page attribute of decorator defines the name of the decorator resource file
the pattern element defines the url pattern for which the decorator will be applied to (you can only have one pattern per decorator)
Decorator resource file
Decorator files are written in the Velocity templating language and have the VMD extension. The following is a sample decorator file:
<html>
<head>
<title>$title</title>
#standardHeader()
</head>
<div id="PageContent">
<table class="pagecontent" border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td valign="top" class="pagebody">
<div class="pageheader">
<span class="pagetitle">$title</span>
</div>
$body
</td>
</tr>
</table>
</div>
</body>
</html>
You can familiarise yourself with Velocity at the Velocity Template Overview and decorators in general at the Sitemesh homepage.
Editor Module
Available:
Confluence 2.5 and later
Editor plugin modules allow you to implement Wysiwyg editors for editing Confluence pages. Currently, Confluence only supports the use of
one wysiwg editor during editing even if there are multiple editor plugins enabled. It is not guaranteed that any one editor will be used over
another, hence it is recommended that only one editor is enabled at a time.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
Editor Plugin Module
The following is a snippet of atlassian-plugin.xml from the TinyMCE Editor:
<atlassian-plugin key="com.atlassian.confluence.extra.tinymceplugin" name="TinyMCE Editor Plugin">
<plugin-info>
<description>TinyMCE Editor Plugin for Confluence</description>
<version>${project.version}</version>
<vendor name="Atlassian" url="http://www.atlassian.com"/>
</plugin-info>
...
<editor name="tinymceeditor"
class="com.atlassian.confluence.extra.tinymceplugin.TinyMceEditor" key="tinymceeditor">
<description>TinyMCE Editor</description>
</editor>
...
</atlassian-plugin>
The class attribute defines the Java class for which the editor will interact with Confluence. This class must implement
com.atlassian.confluence.plugin.editor.
Editor Interface
All editors must implement the following interface:
package com.atlassian.confluence.plugin.editor;
/**
* This interface allows Wysiwyg editors to be plugged in to Confluence.
*/
public interface Editor
{
/**
* Returns javascript functions to allow thw wiki-textarea.vm to interface with the editor.
*
* The Javascript returned must define the functions:
*
* onShowEditor() -- this is called just after the DIV containing the editor is made visible.
It is a hook where you
*
can place any special code needed at this point.
* onHideEditor() -- this is called just before the DIV containing the editor is hidden. It is
a hook where you
*
can place any special code needed at this point.
* setEditorValue(newValue) -- put the text in newValue into the editor. This is called when
the editor needs new
*
content -- it is *not* called to set the initial content. That should be done either
by providing the
*
editor with the content as part of the initial HTML, or by calling javascript from
editorOnLoad().
* allowModeChange() -- return true if the editor is in a state where changes from rich text
to markup and vice versa are allowed.
* getEditorHTML() -- return the current HTML contents of the editor. This *must* return a
JavaScript string,
*
not a JavaObject wrapping a java.lang.String!
* editorOnLoad() -- called in the page's onLoad handler, place any initialization needed at
this point here.
* editorHasContentChanged() -- return true if the contents of the editor has been modified by
the user since
*
the last time editorResetContentChanged().
* editorResetContentChanged() -- called to reset the contents change indicator
*
* These methods won't be called when the editor is not visible.
*
* The javascript must be surrounded by a <script> element.
* @return a String containing a velocity template
*/
String getJavascriptTemplate();
/**
* Returns the div contents to display the editor itself.
* @return a String containing a velocity template
*/
String getDivContentsTemplate();
/**
* Return true if the user agent string indicates a browser which is supported by this editor
* @param userAgent
* @return true if this editor is supported
*/
boolean supportedUserAgent(String userAgent);
/**
* Perform any necessary escaping of the HTML rendered by Confluence. The
AbstractPreviewPageAction.getWysiwygContent()
*
method uses this method to escape the rendered HTML.
*/
String escapeHtml(String html);
/**
* Return a string of CSS which will be appended to the standard stylesheet if it is requested
from /styles/wysiwyg-action
*
* Note that it is up to the editor implementation to retrieve this stylesheet and apply it to
the editor contents -* the page containing the editor is styled with the normal stylesheet.
*/
String getEditorSpecificCss();
}
For an example, you can view the source for the TinyMCE Plugin.
Event Listener Module
Available:
Confluence 1.4 and later
Changed:
In Confluence 3.3 and later, Confluence events are annotation-based. This means that you have two options
when writing event listeners. Option 1 is the Event Listener plugin module, using the
com.atlassian.event.EventListener class. Option 2 is to declare your listener as a component and use
annotation-based event listeners, annotating your methods to be called for specific event types. The component
will need to register itself at the time it gets access to the EventPublisher, typically in the constructor. More
information and examples are below.
Every time something important happens within Confluence (a page is added or modified, the configuration is changed, etc.), an 'event' is
triggered. Listeners allow you to extend Confluence by installing code that responds to those events.
Plugin Events
It is possible to listen for plugin install/uninstall/enable/disable events, however this will be unreliable when trying to listen
for events about your own plugin. You will not receive a PluginDisableEvent or PluginUninstallEvent for the plugin itself. To
trigger actions for these events, one (or more) of your modules (macro, event listener, etc.) should implement the Making
your Plugin Modules State Aware interface instead.
Synchronous Events
Confluence events are currently processed synchronously. That is, Confluence will wait for your event to finish processing
before returning from the method that was the source of the event. This makes it very important that any event listener
you write completes as quickly as possible.
Adding a Listener Plugin
Listeners are a kind of Confluence plugin module.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
Option 1. Annotation Based Event Listeners
Events 2.0
From Confluence 3.3 you can take advantage of annotation-based event listeners.
Using annotation-based event listeners allows you to annotate methods to be called for specific event types. The annotated methods must
take a single parameter specifying the type of event that should trigger the method.
You must also register the class with the EventPublisher, which can be injected to the listener.
As an annotation-based event listener is not required to implement the com.atlassian.event.EventListener interface, you
cannot use the listener module descriptor.
In order to use the annotation-based event listeners you must register your listener as a component. For example:
<atlassian-plugin key="listenerExample" name="Listener Examples" plugins-version="2">
<plugin-info>
<description>${project.description}</description>
<version>${project.version}</version>
<vendor name="${project.organization.name}" url="${project.organization.url}" />
</plugin-info>
<component name="Annotated Event Listener"
class="com.atlassian.confluence.plugin.example.listener.AnnotatedListener"
key="annotatedListener"/>
</atlassian-plugin>
See the example code for how to implement this listener: Annotation Based Event Listener Example
Option 2. The Listener Plugin Module
The Listener Plugin XML
Each listener is a plugin module of type 'listener', packaged with whatever Java classes and other resources the listener requires in order to
run. Here is an example atlassian-plugin.xml file containing a single listener:
<atlassian-plugin name='Optional Listeners' key='confluence.extra.auditor'>
<plugin-info>
<description>Audit Logging</description>
<vendor name="Atlassian Software Systems" url="http://www.atlassian.com"/>
<version>1.0</version>
</plugin-info>
<listener name='Audit Log Listener' class='com.example.listener.AuditListener'
key='auditListener'>
<description>Provides an audit log for each event within Confluence.</description>
</listener>
</atlassian-plugin>
The listener module definition has no configuration requirements beyond any other module: just give it a name, a key, and provide the name
of the class that implements the listener.
The Listener Class
The class attribute of the listener module definition must refer to a Java class that implements the
com.atlassian.confluence.event.EventListener interface. This is the interface:
package com.atlassian.confluence.event;
import com.atlassian.confluence.event.events.ConfluenceEvent;
/**
* Defines a listener for Confluence events.
*/
public interface EventListener
{
/**
* Perform some action as a response to a Confluence event. The EventManager will
* ensure that this is only called if the class of the event matches one of the
* classes returned by getHandledEventClasses
*
* @param event some event triggered within Confluence
*/
void handleEvent(ConfluenceEvent event);
/**
* Determine which event classes this listener is interested in.
*
* The EventManager performs rudimentary filtering of events by their class. If
* you want to receive only a subset of events passing through the system, return
* an array of the Classes you wish to listen for from this method.
*
* For the sake of efficiency, only exact class matches are performed. Sub/superclassing
* is not taken into account.
*
* Returning an empty array will allow you to receive every event.
*
* @return An array of the event classes that this event listener is interested in,
*
or an empty array if the listener should receive all events. <b>Must not</b>
*
return null.
*/
Class[] getHandledEventClasses();
}
A more detailed example, with sample code, can be found in Writing an Event Listener Plugin Module.
Events and Event Types
All events within Confluence extend from com.atlassian.com.event.events.ConfluenceEvent. In general, we use the following convention for
naming each type of ConfluenceEvent:
<Object><Operation>Event
For example, we have the following event types relating to space events: SpaceCreateEvent, SpaceUpdateEvent, SpaceRemoveEvent
. In the above description space would correspond to <Object> and create, update, or remove would correspond to <Operation>.
Occasionally, an operation is so singular that its meaning will be obvious without use of this naming convention; for example a LoginEvent
or ConfigurationEvent.
Limitations of Events
Events are a notification that something has occurred. The event system is not designed to allow a listener to veto the action that
caused the event.
There is no loop detection. If you write a listener for the SpaceModifiedEvent that itself causes a SpaceModifiedEvent to be
generated, you are responsible for preventing the ensuing infinite loop.
Annotation Based Event Listener Example
Available:
Confluence 3.3 and later
This page gives the example code for an annotation-based event listener, as described in the page about event listener plugins.
The methods must be annotated with the com.atlassian.event.api.EventListener annotation and only take a single parameter of
the type of event this method is to service.
The event listener must also register itself with the EventPublisher which can be injected into the constructor. The event listener will need
to implement the DisposableBean interface to unregister itself when the plugin is disabled or uninstalled.
package com.atlassian.confluence.plugin.example.listener;
import
import
import
import
import
import
import
com.atlassian.confluence.event.events.security.LoginEvent;
com.atlassian.confluence.event.events.security.LogoutEvent;
com.atlassian.event.api.EventListener;
com.atlassian.event.api.EventPublisher;
org.slf4j.Logger;
org.slf4j.LoggerFactory;
org.springframework.beans.factory.DisposableBean;
public class AnnotatedListener implements DisposableBean{
private static final Logger log = LoggerFactory.getLogger(AnnotatedListener.class);
protected EventPublisher eventPublisher;
public AnnotatedListener(EventPublisher eventPublisher) {
this.eventPublisher = eventPublisher;
eventPublisher.register(this);
}
@EventListener
public void loginEvent(LoginEvent event) {
log.error("Login Event: " + event);
}
@EventListener
public void logoutEvent(LogoutEvent event) {
log.error("Logout Event: " + event);
}
// Unregister the listener if the plugin is uninstalled or disabled.
public void destroy() throws Exception
{
eventPublisher.unregister(this);
}
}
An annotation-based event listener does not need to implement the com.atlassian.event.EventListener class.
Writing an Event Listener Plugin Module
Available:
Confluence 1.4 and later
Changed:
In Confluence 3.3 and later, Confluence events are annotation-based. This means that you have two options
when writing event listeners. Option 1 is the Event Listener plugin module, using the
com.atlassian.event.EventListener class. Option 2 is to declare your listener as a component and use
annotation-based event listeners, annotating your methods to be called for specific event types. The component
will need to register itself at the time it gets access to the EventPublisher, typically in the constructor. More
information and examples are in Event Listener Module.
Overview
For an introduction to event listener plugin modules, please read Event Listener Module.
Writing an Event Listener as a plugin module within Confluence
Writing an event listener is a four-step process:
1. Identify the events you wish to listen for
2. Create the EventListener Java class
a. Implement getHandledEventClasses()
b. Implement handleEvent()
3. Add the listener module to your atlassian-plugin.xml file
Identify the events you wish to listen for
The easiest thing here is to consult the latest API, in the package com.atlassian.confluence.event.events . When you implement an
EventListener you will provide an array of Class objects which represent the events you wish to handle.
The naming of most events are self explanitory (GlobalSettingsChangedEvent or ReindexStartedEvent for example), however there are
some which need further clarification:
Event Class
Published
LabelCreateEvent
On the creation of the first label to the target Content Entity Object.
LabelRemoveEvent
On the removal of the last label from the target Content Entity Object.
LabelAddEvent
On the addition of any label to the target Content Entity Object.
LabelDeleteEvent
On the deletion of any label from the target Content Entity Object.
Create the EventListener
The EventListener interface defines two methods which must be implemented: getHandledEventClasses() and handleEvent().
Implement getHandledEventClasses()
The getHandledEventClasses() method holds an array of class objects representing the events you wish to listen for.
Your listener will only receive events of the types specified in getHandledEventClasses()
You must specify all the event types you need - specifying a superclass will not include its subclasses
Returning an empty array will cause your listener to receive every event Confluence produces
So, if you want your listener to receive only SpaceCreatedEvent and SpaceRemovedEvent
private static final Class[] HANDLED_EVENTS = new Class[] {
SpaceCreateEvent.class, SpaceRemovedEvent.class
};
public Class[] getHandledEventClasses()
{
return HANDLED_EVENTS;
}
Alternatively, to receive all possible events:
/**
* Returns an empty array, thereby handling every ConfluenceEvent
* @return
*/
public Class[] getHandledEventClasses()
{
return new Class[0];
}
Implement handleEvent()
The implementation below simply relies upon the toString() implementation of the event and logs it to a log4j appender.
public void handleEvent(Event event)
{
if (!initialized)
initializeLogger();
log.info(event);
}
Most often, a handleEvent(..) method will type check each event sent through it and execute some conditional logic.
public void handleEvent(Event event)
{
if (event instanceof LoginEvent)
{
LoginEvent loginEvent = (LoginEvent) event;
// ... logic associated with the LoginEvent
}
else if (event instanceof LogoutEvent)
{
LogoutEvent logoutEvent = (LogoutEvent) event;
// ... logic associated with the LogoutEvent
}
}
A full example of an EventListener class that listens for login and logout events can be found in EventListener Example.
Add the EventListener as a module to your plugin by creating an atlassian-plugin.xml
The atlassian-plugin.xml file has been described elsewhere in detail. This is an example of a listener plugin module included in an
atlassian-plugin.xml file.
<atlassian-plugin name='Optional Listeners' key='confluence.extra.auditor'>
<plugin-info>
<description>Audit Logging</description>
<vendor name="Atlassian Software Systems" url="http://www.atlassian.com"/>
<version>1.0</version>
</plugin-info>
<listener name='Audit Log Listener'
class='com.atlassian.confluence.extra.auditer.AuditListener' key='auditListener'>
<description>Provides an audit log for each event within Confluence.</description>
</listener>
</atlassian-plugin>
EventListener Example
Available:
Confluence 1.4 and later
Changed:
In Confluence 3.3 and later, Confluence events are annotation-based. This means that you have two options
when writing event listeners. Option 1 is the Event Listener plugin module, using the
com.atlassian.event.EventListener class. Option 2 is to declare your listener as a component and use
annotation-based event listeners, annotating your methods to be called for specific event types. The component
will need to register itself at the time it gets access to the EventPublisher, typically in the constructor. More
information and examples are in Event Listener Module.
This page gives the example code for an event listener, as described in the page about event listener plugins.
Below is an example of an EventListener that listens for the LoginEvent and LogoutEvent.
package com.atlassian.confluence.extra.userlister;
import
import
import
import
import
com.atlassian.confluence.event.events.security.LoginEvent;
com.atlassian.confluence.event.events.security.LogoutEvent;
com.atlassian.event.Event;
com.atlassian.event.EventListener;
org.apache.log4j.Logger;
public class UserListener implements EventListener {
private static final Logger log = Logger.getLogger(UserListener.class);
private Class[] handledClasses = new Class[]{ LoginEvent.class, LogoutEvent.class};
public void handleEvent(Event event) {
if (event instanceof LoginEvent) {
LoginEvent loginEvent = (LoginEvent) event;
log.info(loginEvent.getUsername() + " logged in (" + loginEvent.getSessionId() +
")");
} else if (event instanceof LogoutEvent) {
LogoutEvent logoutEvent = (LogoutEvent) event;
log.info(logoutEvent.getUsername() + " logged out (" + logoutEvent.getSessionId() +
")");
}
}
public Class[] getHandledEventClasses() {
return handledClasses;
}
}
Extractor Module
Available:
Confluence 1.4 and later
Extractor plugins allow you to hook into the mechanism by which Confluence populates its search index. Each time content is created or
updated in Confluence, it is passed through a chain of extractors that assemble the fields and data that will be added to the search index for
that content. By writing your own extractor you can add information to the index.
Extractor plugins can be used to extract the content from attachment types that Confluence does not support,
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
Extractor plugins are closely tied to the API of the Lucene Java library
Confluence's internal search is built on top of the Lucene Java library. While familiarity with Lucene is not an absolute
requirement for writing an extractor plugin, you'll need it to write anything more than the most basic of plugins.
Extractor Plugins
Here is an example atlassian-plugin.xml file containing a single search extractor:
<atlassian-plugin name="Sample Extractor" key="confluence.extra.extractor">
...
<extractor name="Page Metadata Extractor" key="pageMetadataExtractor"
class="confluence.extra.extractor.PageMetadataExtractor" priority="1000">
<description>Extracts certain keys from a page's metadata and adds them to the search
index.</description>
</extractor>
...
</atlassian-plugin>
the class attribute defines the class that will be added to the extractor chain. This class must implement
bucket.search.lucene.Extractor
the priority attribute determines the order in which extractors are run. Extractors are run from the highest to lowest priority.
Extractors with the same priority may be run in any order.
As a general rule, all extractors should have priorities below 1000, unless you are writing an extractor for a new attachment
type, in which case it should be greater than 1000.
If you are not sure what priority to choose, just go with priority="900" for regular extractors, and priority="1200"
for attachment content extractors.
To see the priorities of the extractors that are built into Confluence, look in
WEB-INF/classes/plugins/core-extractors.xml and
WEB-INF/classes/plugins/attachment-extractors.xml. From Confluence-2.6.0, these files are packaged inside
confluence-2.6.0.jar; we have instructions for Editing Files within JAR Archives if you're unfamiliar with the process.
The Extractor Interface
All extractors must implement the following interface:
package bucket.search.lucene;
import bucket.search.Searchable;
import org.apache.lucene.document.Document;
public interface Extractor
{
public void addFields(Document document, StringBuffer defaultSearchableText, Searchable
searchable);
}
The document parameter is the Lucene document that will be added to the search index for the object that is being saved. You can
add fields to this document, and the fields will be associated with the object in the index.
The defaultSearchableText is the main body of text that is associated with this object in the search index. It is stored in the
index as a Text field with the key "content". If you want to add text to the index such that the object can be found by a regular
Confluence site search, append it to the defaultSearchableText. (Remember to also append a trailing space, or you'll confuse the
next piece of text that's added!)
The searchable is the object that is being saved, and passed through the extractor chain.
Attachment Content Extractors
If you are writing an extractor that indexes the contents of a particular attachment type (for example, OpenOffice documents or Flash files),
you should extend the abstract class bucket.search.lucene.extractor.BaseAttachmentContentExtractor. This class ensures
that only one attachment content extractor successfully runs against any file (you can manipulate the priorities of attachment content
extractors to make sure they run in the right order).
For more information, see: Attachment Content Extractor Plugins
An Example Extractor
The following example extractor is untested, but it associates a set of page-level properties with the page in the index, both as part of the
regular searchable text, and also as Lucene Text fields that can be searched individually, for example in a custom {abstract-search} macro.
package com.example.extras.extractor;
import
import
import
import
import
import
import
bucket.search.lucene.Extractor;
bucket.search.Searchable;
org.apache.lucene.document.Document;
org.apache.lucene.document.Field;
com.atlassian.confluence.core.ContentEntityObject;
com.atlassian.confluence.core.ContentPropertyManager;
com.opensymphony.util.TextUtils;
public class ContentPropertyExtractor implements Extractor
{
public static final String[] INDEXABLE_PROPERTIES = {"status", "abstract"};
private ContentPropertyManager contentPropertyManager;
public void addFields(Document document, StringBuffer defaultSearchableText, Searchable
searchable)
{
if (searchable instanceof ContentEntityObject)
{
ContentEntityObject contentEntityObject = (ContentEntityObject) searchable;
for (int i = 0; i < INDEXABLE_PROPERTIES.length; i++)
{
String key = INDEXABLE_PROPERTIES[i];
String value = contentPropertyManager.getStringProperty(contentEntityObject, key);
if (TextUtils.stringSet(value))
{
defaultSearchableText.append(value).append(" ");
document.add(new Field(key, value, Field.Store.YES,Field.Index.TOKENIZED));
}
}
}
}
public void setContentPropertyManager(ContentPropertyManager contentPropertyManager)
{
this.contentPropertyManager = contentPropertyManager;
}
}
Debugging
There's a really primitive Lucene index browser hidden in Confluence which may help when debugging. You'll need to tell it the filesystem
path to your $conf-home/index directory.
http://yourwiki.example.com/admin/indexbrowser.jsp
Attachment Content Extractor Plugins
Extractor plugin modules are available in Confluence 1.4 and later
Attachment content extractor plugins enable Confluence to index the contents of attachments that it may not otherwise understand. Before
you read this document, you should be familiar with Extractor Plugins.
The BaseAttachmentContentExtractor class
Attachment content extractor plugins must extend the bucket.search.lucene.extractor.BaseAttachmentContentExtractor
base class. The skeleton of this class is:
package bucket.search.lucene.extractor;
import
import
import
import
import
bucket.search.lucene.Extractor;
bucket.search.lucene.SearchableAttachment;
bucket.search.Searchable;
org.apache.lucene.document.Document;
com.opensymphony.util.TextUtils;
import java.io.InputStream;
import java.io.IOException;
public abstract class BaseAttachmentContentExtractor implements Extractor
{
/** You should not have to override this method */
public void addFields(Document document, StringBuffer defaultSearchableText, Searchable
searchable);
/** Override this method if you can not get the functionality you want by overriding
getMatchingContentTypes() and getMatchingFilenameExtensions() */
protected boolean shouldExtractFrom(String fileName, String contentType);
/** Override this method to return the MIME content-types that your plugin knows how to
extract
text from. If you have already overridden shouldExtractFrom(), this method is useless */
protected String[] getMatchingContentTypes()
{
return new String[0];
}
/** Override this method to return the filename extensions that your plugin knows how to
extract
text from. If you have already overridden shouldExtractFrom(), this method is useless */
protected String[] getMatchingFileExtensions()
{
return new String[0];
}
/** Override this method to do the actual work of extracting the content of the attachment.
Your extractor
should return the text that is to be indexed */
protected abstract String extractText(InputStream is, SearchableAttachment attachment) throws
IOException;
}
The first attachment content extractor that returns true from shouldExtractFrom, and a not-null, not-empty String from
extractText() will cause all remaining attachment content extractors not to run against this file. Thus, it's important to
get the priority value for your plugin right, so general, but inaccurate extractors are set to run after specific, more accurate
extractors.
Other (non-attachment) content extractors will still run, regardless.
An Example
This is an example of a hypothetical extractor that extracts the contents of mp3 ID3 tags.
package com.example.extras.extractor;
import.com.hypothetical.id3.Id3Tag
import bucket.search.lucene.extractor.BaseAttachmentContentExtractor;
import bucket.search.lucene.SearchableAttachment;
import java.io.InputStream;
import java.io.IOException;
public class Id3Extractor extends BaseAttachmentContentExtractor
{
public static final String[] MIME_TYPES = {"audio/x-mp3",
"audio/mpeg",
"audio/mp4a-latm"};
public static final String[] FILE_EXTS = {"mp3", "m4a"};
protected String extractText(InputStream is, SearchableAttachment attachment)
throws IOException
{
Id3Tag tag = Id3Tag.parse(is);
return (tag.getTitle() + " " + tag.getArtist() + " "
+ tag.getGenre() + " " + tag.getAlbumTitle());
}
protected String[] getMatchingContentTypes()
{
return MIME_TYPES;
}
protected String[] getMatchingFileExtensions()
{
return FILE_EXTS;
}
}
Extractor Plugins
Gadget Plugin Module
Available:
The Gadget plugin module is available only for OSGi-based plugins in Confluence 3.1 and later.
Atlassian gadgets provide a new way to include external content into a Confluence wiki page. Other Atlassian applications also support the
gadget module type. Please refer to the gadget developer documentation.
Job Module
Available:
Confluence 2.2 and later
Job plugin modules enable you to add repeatable tasks to Confluence, which are in turn scheduled by Trigger Module.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
Job Plugin Module
The Job plugin module adds a simple reusable component within a plugin. At a minimum, the module class must implement Quartz's Job
interface, but for access to Confluence's objects and database you should extend com.atlassian.quartz.jobs.AbstractJob. Jobs are scheduled
with Trigger Module.
Note that at the moment Jobs are not autowired by Spring (see: CONF-20162).
Here is an example atlassian-plugin.xml fragment containing a single Job module:
<atlassian-plugin name="Sample Component" key="confluence.extra.component">
...
<job key="myJob"
name="My Job"
class="com.example.myplugin.jobs.MyJob" perClusterJob="false" />
...
</atlassian-plugin>
the name attribute represents how this component will be referred to in the Confluence interface.
the key attribute represents the internal, system name for your Job. This is what the Trigger will refer to.
the class attribute represents the class of the Job to be created. The class must have a no-argument constructor, or it will not be
able to be instantiated by Confluence.
the perClusterJob attribute, if "true", indicates that this job will only run once when triggered in a cluster rather than once on every
node.
For examples of how to schedule Jobs to be run, see Trigger Module.
Workaround pattern for autowiring jobs
As described in CONF-20162, there is no autowiring for Job Modules. In plugins 1 dependencies could be easily fetched from the
ContainerManager. In plugins 2 this is not always the case. There is a workaround however. Instead of using a job module, use a regular
component module that extends JobDetail.
Module descriptors
In your atlassian-plugin.xml declare the trigger as usual and make it point to a JobDetail that is a component module, rather than a job
module.
<atlassian-plugin>
<!-- ... -->
<component key="sampleJobDetail" name="A sample Job Detail"
class="com.example.SampleJobDetail">
<description>This SampleJobDetail is a component module that will be autowired by
Srping.</description>
<interface>org.quartz.JobDetail</interface>
</component>
<trigger key="sampleJobTrigger" name="Sample Job Trigger">
<job key="sampleJobDetail"/>
<schedule cron-expression="0/2 * * * * ?"/>
</trigger>
<!-- ... -->
</atlassian-plugin>
JobDetail
The Detail object itself is, or can be, fairly trivial. It needs to be autowired with the dependencies you need, and it needs to call the super
constructor with the class of the actual job to run.
/**
* This class allows Spring dependencies to be injected into {@link SampleJob}.
* A bug in Confluence's auto-wiring prevents Job components from being auto-wired.
*/
public class SampleJobDetail extends JobDetail
{
private final SampleDependency sampleDependency;
/**
* Constructs a new SampleJobDetail. Calling the parent constructor is what registers the
{@link SampleJob}
* as a job type to be run.
*
* @param sampleDependency the dependency required to perform the job. Will be autowired.
*/
public SampleJobDetail(SampleDependency sampleDependency)
{
super();
setName(SampleJobDetail.class.getSimpleName());
setJobClass(SampleJob.class);
this.sampleDependency = sampleDependency;
}
public SampleDependency getSampleDependency()
{
return sampleDependency;
}
}
The Job
Finally the Job itself can now retrieve anything it wants from the Detail which is retrieved from the jobExecutionContext. It does have to cast
the the appropriate Detail class first.
/**
* Job for doing something on a regular basis.
*/
public class SampleJob extends AbstractJob
{
public void doExecute(JobExecutionContext jobExecutionContext) throws JobExecutionException
{
SampleJobDetail jobDetail = (SampleJobDetail) jobExecutionContext.getJobDetail();
jobDetail.getSampleDependency().doTheThingYouNeedThisComponentFor();
}
}
Working example
You can see an example of this in the WebDAV plugin. Look at ConfluenceDavSessionInvalidatorJob and
ConfluenceDavSessionInvalidatorJobDetail.
Keyboard Shortcut Module
Available:
Confluence 3.4 and later
Purpose of this Module Type
A Keyboard Shortcut plugin module defines a keyboard shortcut within Confluence. A Confluence keyboard shortcut allows you to perform
potentially any action in Confluence using one or more keyboard strokes – for example, going to the dashboard, editing a page, adding a
comment, formatting text while using the editor, and so on.
Configuration
The root element for the Keyboard Shortcut plugin module is keyboard-shortcut. It allows the following attributes and child elements for
configuration:
Attributes
Name
Required
Description
key
Default
N/A
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred.
i18n-name-key
The localisation key for the human-readable name of the plugin module.
name
The human-readable name of the plugin module.
hidden
When hidden='true', the keyboard shortcut will not appear in the Keyboard Shortcuts dialog
box.
The
plugin
key.
false
Despite not appearing in the dialog box, hidden keyboard shortcuts can still be accessed via
the relevant keystrokes.
Elements
Name
order
Required
Description
A value that determines the order in which the shortcut appears on the Keyboard Shortcuts dialog box, with
respect to other keyboard-shortcut plugin modules. This element is also used to override existing shortcuts
displayed on the Keyboard Shortcuts dialog box (see below).
For each keyboard-shortcut plugin module, we recommend using gaps in order values (for example,
10, 20, 30, etc.) rather than consecutive values. This will allow you to insert new keyboard shortcuts more easily
into the keyboard shortcuts dialog box.
description
A human-readable description of this Keyboard Shortcut module. May be specified as the value of this element in
plain text or with the key attribute to use the value of a key from the i18n system.
shortcut
The sequence of keystrokes required to activate the keyboard shortcut. These should be presented in the order
that the keys are pressed on a keyboard. For example, gb represents a keyboard shortcut activated by pressing '
g' then 'b' on the keyboard.
operation
A jQuery selector that specifies the target of the keyboard shortcut. The target is typically a component of the
current page that performs an action. The operation element is accompanied by a type attribute that specifies
the type of keyboard shortcut operation. Available types are:
click
Clicks an element identified by a jQuery selector
execute
Executes a JavaScript function specified by the operation parameter
followLink
Sets the window.location to the href value of the link specified by the jQuery selector.
goTo
Changes the window.location to go to a specified location
moveToAndClick
Scrolls the window to an element and clicks that element using a jQuery selector
moveToAndFocus
Scrolls the window to a specific element and focuses that element using a jQuery selector
moveToNextItem
Scrolls to and adds focused class to the next item in the jQuery collection
moveToPrevItem
Scrolls to and adds focused class to the previous item in the jQuery collection
context
The context defines which pages the shortcut will be active on.
If this element contains global the keyboard shortcut will be active on all Confluence pages. The
shortcut will also appear in the 'Global Shortcuts' sub-section of the Keyboard Shortcuts dialog box under
the 'General' tab.
If this element contains viewcontent the keyboard shortcut will be active when viewing a page or blog
post. The shortcut will also appear in the 'Page / Blog Post Actions' sub-section of the Keyboard
Shortcuts dialog box under the 'General' tab.
If this element contains viewinfo the keyboard shortcut will be active when viewing the info for a page.
The shortcut will also appear in the 'Page / Blog Post Actions' sub-section of the Keyboard Shortcuts
dialog box under the 'General' tab.
Overriding Existing Keyboard Shortcuts
You can override an existing keyboard shortcut defined either within Confluence itself or in another plugin.
To do this create a keyboard-shortcut plugin module with exactly the same shortcut element's keystroke sequence as that of the
keyboard shortcut you want to override. Then, ensure that an order element is added, whose value is greater than that defined in the
keyboard shortcut being overridden.
The order element will affect the position of your overriding keyboard shortcut on the Keyboard Shortcuts dialog box. It will also prevent
the overridden keyboard shortcut from being accessed via the keyboard.
Internationalisation
It is possible to include an i18n resource in your atlassian-plugin.xml to translate keyboard shortcut descriptions into multiple
languages via their 'key' attributes.
Examples
These examples are taken from Confluence's pre-defined keyboard shortcuts:
...
<keyboard-shortcut key="goto.space" i18n-name="admin.keyboard.shortcut.goto.space.name"
name="Browse Space">
<order>20</order>
<description key="admin.keyboard.shortcut.goto.space.desc">Browse Space</description>
<shortcut>gs</shortcut>
<operation type="followLink">#space-pages-link</operation>
<context>global</context>
</keyboard-shortcut>
...
...
<keyboard-shortcut key="managewatchers"
i18n-name="admin.keyboard.shortcut.managewatchers.name" name="Manage Watchers">
<order>60</order>
<description key="admin.keyboard.shortcut.managewatchers.desc">Manage
Watchers</description>
<shortcut>w</shortcut>
<operation type="click">#manage-watchers-menu-item</operation>
<context>viewcontent</context>
</keyboard-shortcut>
...
...
<keyboard-shortcut key="quicksearch" i18n-name="admin.keyboard.shortcut.quicksearch.name"
name="Quick Search">
<order>40</order>
<description key="admin.keyboard.shortcut.quicksearch.desc">Quick Search</description>
<shortcut>/</shortcut>
<operation type="moveToAndFocus">#quick-search-query</operation>
<context>global</context>
</keyboard-shortcut>
...
RELATED TOPICS
Writing Confluence Plugins
Installing a Plugin
Language Module
Available:
Confluence 2.2 and later
To run Confluence in another language, you must install a language pack plugin for that translation. Guides and tools for collaboratively
creating translations have been made available to the Confluence community.
This page provides a technical overview of plugins, for users interested in creating or updating a translation. To install a
translation, please see Installing a Language Pack.
Translations for the Rich Text Editor can be part of a Confluence language pack plugin.
Language Pack Overview
Language plugins are placed in the <CONFLUENCE-INSTALL-DIRECTORY>/languages/<KEY> directory, where <KEY> is the
international language identifier. They consist of three files:
Name
Purpose
Filename
Location
Language Plugin
Descriptor
Defines
language
settings in
language
tag
atlassian-plugin.xml
./src/etc
ConfluenceActionSupport
Properties File
Contains
text strings
in
key:value
mapping
ConfluenceActionSupport<KEY>.properties
./src/etc/com/atlassian/confluence/core
Flag Image
Contains
flag image
for country
<KEY>.png
./src/etc/templates/languages/<KEY>
Directory Structure
The location of the three files that compose a Language Pack plugin is as follows:
./src/etc/com/atlassian/confluence/<PATH_OF_PROPERTIES_FILE>
./src/etc/templates/languages/<LANGUAGE_KEY>/<LANGUAGE_KEY>.gif
./src/etc/atlassian-plugin.xml
As an example, this is the directory listing of the German translation ("de_DE"):
./confluence-2.2-std/plugins/de_DE/src
./confluence-2.2-std/plugins/de_DE/src/etc
./confluence-2.2-std/plugins/de_DE/src/etc/atlassian-plugin.xml
./confluence-2.2-std/plugins/de_DE/src/etc/com
./confluence-2.2-std/plugins/de_DE/src/etc/com/atlassian
./confluence-2.2-std/plugins/de_DE/src/etc/com/atlassian/confluence
./confluence-2.2-std/plugins/de_DE/src/etc/com/atlassian/confluence/core
./confluence-2.2-std/plugins/de_DE/src/etc/com/atlassian/confluence/core/ConfluenceActionSupport_de_DE.properties./
Language Plugin Structure
The three components of a plugin must be updated for each translation. The following sections describe updating the language plugin
descriptor, flag image and ConfluenceActionSupport properties file.
Defining The Language Plugin Descriptor
This is an example atlassian-plugin.xml file for a Language Pack plugin for German:
<atlassian-plugin name='German language pack' key='confluence.languages.de_DE'>
<plugin-info>
<description>This plugin contains translations for the German language</description>
<vendor name="Atlassian Software Systems" url="http://www.atlassian.com"/>
<version>1.0</version>
</plugin-info>
<language name="German" key="de_DE" language="de" country="DE">
<!-- Define a flag that will be shown for this language -->
<resource name="de_DE.gif" type="download" location="templates/languages/de_DE/de_DE.gif">
<property key="content-type" value="image/gif"/>
</resource>
</language>
</atlassian-plugin>
Language Plugin Descriptor Attributes
The atlassian-plugin.xml file declares the language being bundled using the following attributes:
Attribute
Description
Required
language
The language being defined
Yes
country
The country the language belongs to
No
variant
The variant of the language
No
These values are based off those defined in the java.util.Locale class. For information on the valid values for the language, country and
variant attributes, please see the java.util.Locale documentation.
The key attribute is an aggregation of the the three previous attributes, in the same format as that of java.util.Locale:
language[DOC:_country][DOC:_variant]
Flag Images
Language packs define a flag that is to be used to represent the language. The atlassian-plugin.xml defines the language property:
<resource name="en_AU.gif" type="download" location="templates/languages/en_AU/en_AU.gif">
<property key="content-type" value="image/gif"/>
</resource>
When selecting a language, the flag defined above will be displayed. Additionally, the flag will appear during the setup process.
ConfluenceActionSupport Properties File
This Java Properties file contains key-value pairs for each string in Confluence, and supports variables. For example:
remove.all.name=Remove All
view.mail.thread.desc.full=Entire Thread (Showing {0} of {1})
Creating A New Confluence Translation
Caveat emptor!
This page is outdated and don't cover all the files to be translated.
If you would like to translate Confluence into your local language, follow the instructions below on creating a language pack plugin from an
example.
The Confluence community is sharing their in-progress and complete translations. You should check that a shared translation to your target
language has not already been started here.
Preparation
Start by checking out the technical overview of a Language Pack Plugin. Once you are familiar with the structure and content of a plugin, you
can move on to creating your own:
1. Check that you have the latest version of Confluence here. If not, you are recommended to install the latest version for translation,
though you can use any version newer than 2.2. Refer to the guide on Upgrading Confluence for instructions.
2. If you do not already have Apache Ant installed, download the latest version and setup your environmental variables according to the
manual
3. If you are using Confluence 2.2.0 only, you will need to unzip the language plugin base files from languages.zip into a subdirectory of
<CONFLUENCE-INSTALL-DIRECTORY> called languages
Modifying The Example Language Pack Settings
This example will work from an example plugin en_AU.zip. (or better: UPDATED Confluence 3.2 ConfluenceActionSupport.properties )
1. Unzip the example en_AU language pack en_AU.zip into a subdirectory of <CONFLUENCE-INSTALL-DIRECTORY>/languages
called en_AU. Note that is the file is just a renamed copy of default English properties file
2.
1.
2. We will now update the properties file in the example to the latest version. Open your Confluence install directory and copy the
confluence\WEB-INF\classes\com\atlassian\confluence\core\ConfluenceActionSupport.properties file to
the example plugin directory src\etc\com\atlassian\confluence\core.
3. Remove the old ConfluenceActionSupport_en_AU.properties file, and rename
ConfluenceActionSupport.properties to ConfluenceActionSupport_en_AU.properties.
4. Locate the plugin descriptor file, ConfluenceActionSupport properies file and flag image
<CONFLUENCE-INSTALL-DIRECTORY>/languages/en_AU/src/etc/atlassian-plugin.xml
<CONFLUENCE-INSTALL-DIRECTORY>/languages/en_AU/src/etc/com/atlassian/confluence/core/ConfluenceActionSupport_
5. Detemine your language plugin key <KEY> using your country and locale according to the Language Module guide
6. Atlassian has licensed a set of flags for use with translations. Delete en_AU.png and download the appropriate flag from Language
Pack Flags, renaming it to the correct key
7. Update atlassian-plugin.xml to contain the relevant <KEY> and other references, including image type. Refer to the first
section from the above Language Module for help on deciding what to modify
8. Rename the directory structure and filenames that contain en-AU to your own <KEY>. The directory should now appear as
<CONFLUENCE-INSTALL-DIRECTORY>/languages/<KEY>/src/etc/atlassian-plugin.xml
<CONFLUENCE-INSTALL-DIRECTORY>/languages/<KEY>/src/etc/com/atlassian/confluence/core/ConfluenceActionSupport<
You are now ready to build the plugin with the default English text to check that your setup is are correct. These next few steps
deploy the default English version of the pack under your own language
9. From the command line, go to <CONFLUENCE-INSTALL-DIRECTORY>/languages and execute
ant -Dlanguage=<KEY> build
10. Copy <CONFLUENCE-INSTALL-DIRECTORY>/plugins/<KEY>/dist/languages-<KEY>.jar to
<CONFLUENCE-INSTALL-DIRECTORY>/confluence/WEB-INF/lib
11. Restart Confluence
12. From your browser, login as an Administrator, then go to Administration -> Language and verify that you are able to select the
translation
Updating The Language Pack
To collaborate on the translation process, you may wish to upload your translation to the Community Translations page. Repeat these
instructions to test each iteration of your translation attempt.
1. Unzip excelbundle0.9.zip to your local drive.
2. Browse to your Confluence install and go to the \confluence\WEB-INF\classes\com\atlassian\confluence\core
directory. Copy the ConfluenceActionSupport.properties file there into the translation_tool directory and rename it to
ConfluenceActionSupport_en.properties.
3. If you want to start a fresh translation, skip this step. To work from an existing translation, copy it into the translation_tool
directory and remove any country variant from the filename, eg ConfluenceActionSupport_ru_RU.properties becomes
ConfluenceActionSupport_ru.properties.
4. Call the translation tool to create the spreadsheet file. For example, to create a Russian translation, open a terminal window in the
translation_tool directory and call
Edit the file content, referring to Translating ConfluenceActionSupport Content for more information on how to modify the string values.
Call the translation tool to export the updates back into the localised properties file. For the example Russian translation, open a terminal
window, go to the translation_tool directory and call
Once you have completed editing, you must copy and rename the localised translation back to the language plugin directory. For frequent
updates, you may wish to create a script to do this.
To view the updates after copying across the new properties file, select the language plugin for your translation, then restart Confluence and
refresh your browser.
Building The Language Pack Plugin
To build the new language pack plugin, execute Ant in the confluence\src\etc\languages directory:
ant -Dlanguage=<LANGUAGE> build
A JAR will be created in the languages/<LANGUAGE>/dist/ folder.
Installation On A Confluence Server
To install the translation in another instance of Confluence.
1. Copy languages-<KEY>.jar into the <CONFLUENCE-INSTALL-DIRECTORY>/confluence/WEB-INF/lib of your target
installation
2. Restart Confluence
3. From your browser, login as an Administrator, then go to Administration -> Language and select the translation
Submitting A Translation (Optional)
If you would like to share your completed translation with other Confluence users, you can upload it here.
By providing Atlassian permission to bundle complete translations with the Confluence install you will soon be able to select your local
language from the Confluence translations list under System Administration, without needing to package it as a plugin.
Language Pack Flags
Below are flags that can be used with Language Pack Plugins in Confluence. For individual country names, see the DOC:attachments list.
These images are only for us within Confluence plugins and may not be redistributed with any other code. For license
details, see license.txt
Translating ConfluenceActionSupport Content
Guide for translating the values for each property in a ConfluenceActionSupport_<KEY>.properties file, where <KEY> is the
international language identifier:
Translating Strings Without Variables Or Links
These links can be translated directly. Using German in this example
submit.query.name=Submit Query
can be translated directly into
submit.query.name=Anfrage senden
Translating Strings Containing Variables Or Links
Some strings use variables or hyperlinks to provide contextual information. Variables are shown as {NUMBER} while hyperlinks are shown as
<a href="{NUMBER}">LINK ALIAS</a>. Translations must take into account the positioning of variables, and check that links occur
over the relevant phrase. Using German again as an example
search.include.matches.in.other.spaces=There are <b>{0} matches</b> in <b>other spaces</b>. <a
href="{1}">Include these matches</a>.
This tag uses a variable to show the number of matches, and a link the user can click to include those matches. The German version must
place the 'matches' variable in the adjusted location, and reapply the hyperlink to the relevant phrase.
search.include.matches.in.other.spaces=Es wurden <b>{0} Resultate</b> in <b>anderen Spaces</b>
gefunden. <a href="{1}">Diese Resultate einschliessen</a>.
Translations for the Rich Text Editor
The Rich Text Editor provided by Confluence is TinyMCE. In Confluence version 2.2.10 and above it is possible to provide translations for the
tooltips and labels in the Rich Text Editor.
Most of the editor's internationalised text consists of its tooltips. There are also a few labels such as those in the Image Properties dialog. If
you are using Confluence in a language other than English, you will want to translate these messages as well as the standard Confluence
text.
Confluence fully supports internationalisation of the rich text editor:
The translations for the rich text editor can be part of a Confluence language pack plugin. The TinyMCE properties can be included
in the ConfluenceActionSupport properties file, along with the standard Confluence properties.
If your language pack does not contain translations for the rich text editor, the text will show in English.
In Confluence versions 2.5.4 and earlier, Rich Text Editor translations can not be installed as a language pack.
Refer to earlier documentation for a workaround.
Creating a new translation
The core editing strings for the Rich Text Editor translations are found in the tinymce.properties file.
Add a new i18n plugin resource to atlassian-plugin.xml like this:
<resource name="i18n" type="i18n" location="com/atlassian/confluence/tinymceplugin/tinymce"/>
Now, put your translations (as described below) in tinymce_locale.properties (where locale is the target locale - e.g. de_DE) under
the directory src/main/resources/com/atlassian/confluence/tinymceplugin.
Example
Below is a partial listing of the core TinyMCE properties. The properties consist of 'key=value' pairs. To translate from English to another
language, you would replace the text to the right of the '=' sign with the translation.
# English
## TinyMCE core
tinymce.bold_desc=Bold (Ctrl+B)
tinymce.italic_desc=Italic (Ctrl+I)
tinymce.underline_desc=Underline (Ctrl+U)
tinymce.striketrough_desc=Strikethrough
.
.
.
## paste plugin
tinymce.paste.paste_text_desc=Paste as Plain Text
tinymce.paste.paste_text_title=Use CTRL+V on your keyboard to paste the text into the window.
tinymce.paste.paste_text_linebreaks=Keep linebreaks
.
.
.
Updating A Confluence Translation
This guide is for translating Confluence into non-English languages using a Spreadsheet, and covers:
1. Improving or finishing a translation for an existing Language Plugin
2. Updating an existing translation for a new version of Confluence
If you do not have a Language Plugin to deploy the updated ConfluenceActionSupport_<KEY>.properties file (where <KEY> is the
international language identifier), you should instead go to the Creating A New Confluence Translation.
To make small updates, it is quicker to translate the file directly. If your changes are more substantial, you may prefer to translate using
Excel.
Translating Directly
This approach uses any file editor. If your translation uses English characters, you can skip to the next section.
Preparing Non-Unicode Files For Direct Translation
If you do not have the Sun Java JDK installed, please download it now. Version 5.0 can be downloaded here.
1. Create a script or batch file that uses the native2ascii.exe program bundled in <JAVA-JDK-DIRECTORY>/bin to convert from the
natively encoded file back to the Unicode file. For example, update the Russian properties file with a script or batch file that calls
native2ascii -encoding cp1251 JiraWebActionSupport_ru_RU-native.txt
JiraWebActionSupport_ru_RU.properties
2. Copy ConfluenceActionSupport<KEY>.properties to a new file ConfluenceActionSupport<KEY>-native.txt. Save
the new file local non-Unicode character encoding.
Performing Direct Translation
These steps apply to both Unicode and non-Unicode translations:
1. Open the properties file (or it's natively encoded equivalent) for editing, translate some or all of the properties file into your target
language, and save the changes. If you are translating into a non-Unicode language, always edit
ConfluenceActionSupport<KEY>-native.txt, otherwise modify ConfluenceActionSupport<KEY>.properties.
2. Edit the file content in a text editor, referring to Translating ConfluenceActionSupport Content for more information on how to modify
the string values. Users who are unsatisfied with simply opening two copies of the file in their favourite editor may want to try this
freeware properties editor, that allows side-by-side comparisons.
3. For non-Unicode translations only, run the native2ascii script to update ConfluenceActionSupport<KEY>.properties
4. If you wish to test the update, copy the file back to its original location in the plugin. Then restart Confluence.
Translating Using A Spreadsheet
The guide below uses the open-source ExcelBundle, released under the Apache License 2.0. To translate from Excel or OpenOffice:
1. Unzip excelbundle0.9.zip to your local drive.
2. Browse to your Confluence install and go to the \confluence\WEB-INF\classes\com\atlassian\confluence\core
directory. Copy the ConfluenceActionSupport.properties file there into the translation_tool directory and rename it to
ConfluenceActionSupport_en.properties.
3. If you want to start a fresh translation, skip this step. To work from an existing translation, copy it into the translation_tool
directory and remove any country variant from the filename, eg ConfluenceActionSupport_ru_RU.properties becomes
ConfluenceActionSupport_ru.properties.
4. Call the translation tool to create the spreadsheet file. For example, to create a Russian translation, open a terminal window in the
translation_tool directory and call
java -jar excelbundle.jar -export translation_ru.xls -l en,ru -r "%cd%"
5. Edit the file content, referring to Translating ConfluenceActionSupport Content for more information on how to modify the string
values.
6. Call the translation tool to export the updates back into the localised properties file. For the example Russian translation, open a
terminal window, go to the translation_tool directory and call
java -jar excelbundle.jar -import translation_ru.xls -l ru -r "%cd%"
7. Once you have completed editing, you must copy and rename the localised translation back to the language plugin directory. For
frequent updates, you may wish to create a script to do this.
8. To view the updates after copying across the new properties file, select the language plugin for your translation, then restart
Confluence and refresh your browser.
Lifecycle Module
Available:
Confluence 2.3 and later
Lifecycle plugins allow you to perform tasks on application startup and shutdown.
Application Lifecycle
Startup is performed after Confluence has brought up its Spring and Hibernate subsystems. If Confluence is being set up for the first time,
the startup sequence is run after the completion of the setup wizard. This means that lifecycle plugins can assume access to a fully
populated Spring container context, and a working database connection. (i.e. you don't need to check isContainerSetup() or
isSetupComplete())
Shutdown is performed when the application server is shutting down the web application, but before the Spring context is disposed of.
Plugin Activation and Deactivation
Activating or deactivating a lifecycle plugin will not cause any of its lifecycle methods to be run. If you want your plugin to
respond to activation and deactivation, you should make sure it implements Making your Plugin Modules State Aware.
Shutdown is not guaranteed
There are many situations in which the shutdown sequence will not be run, as it is dependent on the orderly shutdown of
the application server. Plugins should not rely on shutdown being performed reliably, or even ever.
Shutdown lifecycle tasks are most useful for cleaning up resources or services that would otherwise leak in situations
where the web application is being restarted, but the JVM is not exiting. (i.e. services that retain classloaders or threads
that would otherwise prevent the application from being garbage-collected)
Defining a Lifecycle Plugin
Lifecycle plugin definitions are quite simple. Here's a sample atlassian-plugin.xml fragment:
<lifecycle key="frobozz" name="Frobozz Service" class="com.example.frobozz.Lifecycle"
sequence="1200">
<description>Start and stop the Frobozz service</description>
</lifecycle>
The key is the required plugin module key, which must be unique within the plugin.
The name is the required display name for the plugin.
The class is the required class name for the lifecycle service implementation.
The sequence number is required, and determines the order in which lifecycle plugins are run. On startup, they are run from lowest
to highest sequence number, then in reverse order on shutdown.
Defining a Lifecycle Service Implementation
If you are implementing a new lifecycle service, you should implement com.atlassian.config.lifecycle.LifecycleItem:
package com.atlassian.config.lifecycle;
public interface LifecycleItem
{
/**
* Called on application startup.
*
* @param context the application's lifecycle context
* @throws Exception if something goes wrong during startup. No more startup items will be
run, and the
*
application will post a fatal error, shut down all LifecycleItems that have run
previously, and
*
die horribly.
*/
void startup(LifecycleContext context) throws Exception;
/**
* Called on application shutdown
*
* @param context the application's lifecycle context
* @throws Exception if something goes wrong during the shutdown process. The remaining
shutdown items
*
will still be run, but the lifecycle manager will log the error.
*/
void shutdown(LifecycleContext context) throws Exception;
}
However, for convenience, and to make it easy to plug in third-party lifecycle events that are implemented as servlet context listeners,
lifecycle service classes can instead implement javax.servlet.ServletContextListener – the contextInitialized() method
will be called on startup, and contextDestroyed() on shutdown.
Sequences
The sequence numbers of the lifecycle modules determine the order in which they are run. On startup, modules are run from lowest to
highest sequence number, then on shutdown that order is reversed (first in, last out). As a general guideline:
Sequence number
Description
0 - 500
Configuration tweaks and application sanity checks.
800
Database and configuration upgrades.
1000
Zen Foundation configuration.
5000
Start/stop the Quartz scheduler
If your startup lifecycle item has a sequence less than 800, you can't assume that the configuration or database schema are current
If you have a sequence number greater than 5000, you must keep in mind that scheduled jobs (including Job Module) may run
before you've started up, or after you've shut down.
Lucene Boosting Strategy Module
Available:
Confluence 3.0 and later
Lucene Boosting Strategy plugins allow you to configure the scoring mechanism used by Lucene to order search results in Confluence. Each
time a document is found via search, it is passed through the set of boosting strategies to determine its score for ranking in the search
results. By writing your own boosting strategy you can customise the order of search results found by Confluence.
Confluence's internal search is built on top of the Lucene Java library. Familiarity with Lucene is a requirement for writing a
boosting strategy plugin, and this documentation assumes you understand how Lucene works.
Lucene Boosting Strategy Plugins
Here is an example atlassian-plugin.xml file containing a single search extractor:
<atlassian-plugin name='Sample Boosting Strategies' key='example.boosting.strategies'>
...
<lucene-boosting-strategy key="boostByModificationDate"
class="com.example.boosting.strategies.BoostByModificationDateStrategy"/>
...
</atlassian-plugin>
the class attribute defines the class that will be called to boost search result scores. This class must implement
com.atlassian.confluence.search.v2.lucene.boosting.BoostingStrategy
The BoostingStrategy Interface
All strategies must implement the following interface, BoostingStrategy:
package com.atlassian.confluence.search.v2.lucene.boosting;
import java.io.IOException;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.FieldCache;
import com.atlassian.confluence.search.service.SearchQueryParameters;
/**
* An implementation of this interface may be passed to {@link BoostingQuery} to achieve an
arbitrary per document score
* boost.
*/
public interface BoostingStrategy
{
/**
* <p>Apply a relevant boost to the specified document with the specified score. Returning a
score
* of 0 will remove the document from the results.</p>
* <p><em>Warning:</em> This method needs to return extremely fast, so any I/O like using the
index reader
* to load the actual document is discouraged. If you need access to a documents field values
you should rather
* consider using a {@link FieldCache} instead.</p>
*
* @param reader a reader instance associated with the current scoring process
* @param doc the doc id
* @param score the original score for the document specified by doc
* @return the boosted score, 0 to remove the document from the results, or
<code>score</score> to make no change to the score
* @throws IOException
*/
float boost(IndexReader reader, int doc, float score) throws IOException;
/**
* <p>Apply a relevant boost to the specified document with the specified score. Returning a
score
* of 0 will remove the document from the results.</p>
* <p><em>Warning:</em> This method needs to return extremely fast, so any I/O like using the
index reader
* to load the actual document is discouraged. If you need access to a documents field values
you should rather
* consider using a {@link FieldCache} instead.</p>
* <p>If you are implementing this method but do not use the
<code>searchQueryParameters</code>, it is safe to delegate
* directly to the <code>boost(IndexReader, int, float)</code> method.</p>
*
* @param reader a reader instance associated with the current scoring process
* @param searchQueryParameters extra state information used by more complex boosting
strategies
* @param doc the doc id
* @param score the original score for the document specified by doc, or <code>score</score>
to make no change to the score
* @return the boosted score or 0 to remove the document from the results
* @throws IOException
*/
float boost(IndexReader reader, SearchQueryParameters searchQueryParameters, int doc, float
score) throws IOException;
}
The reader should not be used to retrieve data directly, otherwise it will be incredibly slow to retrieve search results in
Confluence. The reader should only be used with the FieldCache object to retrieve a cache of values from the index. See
the example and discussion below.
An Example Boosting Strategy
The following boosting strategy is used in Confluence to boost search results by last-modified date. Some of the logic to do with
date-handling has been removed to simplify the example.
package com.example.boosting.strategies;
import java.io.IOException;
import java.util.Calendar;
import java.util.Date;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.FieldCache;
import com.atlassian.bonnie.LuceneUtils;
import com.atlassian.confluence.search.service.SearchQueryParameters;
import com.atlassian.confluence.search.v2.lucene.boosting.BoostingStrategy;
/**
* A {@link BoostingStrategy} that boost the scores based on the modification date of scored
document. Recently modified
* Document get a higher boost.
*/
public class BoostByModificationDateStrategy implements BoostingStrategy
{
static final String MODIFIED_FIELD = "modified";
private
private
private
private
private
private
private
static
static
static
static
static
static
static
final
final
final
final
final
final
final
float
float
float
float
float
float
float
BOOST_TODAY = 1.5f;
BOOST_YESTERDAY = 1.3f;
BOOST_WEEK_AGO = 1.25f;
BOOST_MONTH_AGO = 1.2f;
BOOST_THREE_MONTH_AGO = 1.15f;
BOOST_SIX_MONTH_AGO = 1.10f;
BOOST_ONE_YEAR_AGO = 1.05f;
public float boost(IndexReader reader, int doc, float score) throws IOException
{
String[] fieldcaches = FieldCache.DEFAULT.getStrings(reader, MODIFIED_FIELD);
// more recent hits get a boost
Date age = LuceneUtils.stringToDate(fieldcaches[doc]);
score *= getAgeBoostFactor(age);
return score;
}
public float boost(IndexReader reader, SearchQueryParameters searchQueryParameters, int doc,
float score) throws IOException
{
return boost(reader, doc, score);
}
private float getAgeBoostFactor(Date date)
{
// ... irrelevant Date/Calendar mangling ...
float boostFactor;
if (date.after(startOfToday))
boostFactor = BOOST_TODAY;
else if (date.after(startOfYesterday))
boostFactor = BOOST_YESTERDAY;
else if (date.after(startOfWeekAgo))
boostFactor = BOOST_WEEK_AGO;
else if (date.after(oneMonthAgo))
boostFactor = BOOST_MONTH_AGO;
else if (date.after(threeMonthsAgo))
boostFactor = BOOST_THREE_MONTH_AGO;
else if (date.after(sixMonthsAgo))
boostFactor = BOOST_SIX_MONTH_AGO;
else if (date.after(oneYearAgo))
boostFactor = BOOST_ONE_YEAR_AGO;
else
boostFactor = 1;
return boostFactor;
}
}
Using Field Caches
Note that this example uses a Lucene FieldCache, which stores a copy of all the modification data for all index entries in memory. If you are
implementing a BoostingStrategy yourself, you should also use a FieldCache (rather than reading the index entries from disk) and be aware
of their behaviour:
the first time you use a field cache, it requires iterating through every index entry to warm up the cache in a synchronised block
field caches are cleared every time the search index is updated (normally every minute in Confluence), which requires another
warm-up
field caches keep a copy of each term in memory, usually requiring a large amount of memory.
Be sure to measure the increase in memory usage required after installing your plugin and how well your custom boosting strategy copes
with a large amount of data in the index that is updated every minute.
Confluence itself has only two active field caches: one for the "modified" field in the main index (as shown above), and one for "word" in the
Did-You-Mean index. When a new Searcher is created after each write to the index, Confluence manually warms up the "modified" field
cache with the following call:
FieldCache.DEFAULT.getStrings(searcher.getIndexReader(), "modified");
It might improve performance to warm up any field caches when your plugin is initialised. There's currently no way for a plugin to determine
when IndexSearchers are refreshed, so there may be a relatively frequent performance hit if you are accessing a FieldCache which hasn't
been warmed up.
Related Pages
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
Boosting Strategy plugins are closely tied to the API of the Lucene Java library
Extractor Modules are a related plugin module type for the Confluence search system.
Macro Module
Available:
Confluence 1.3 and later
Macros are Confluence code that can be invoked from inside a page by putting the name of the macro in curly brackets. Users of Confluence
will be familiar with macros like {color} or {children} or {rss}. Thanks to the plugin system, it is easy to write and install new macros into a
Confluence server.
As of Confluence 4.0, all macros must contain metadata in order to function correctly in Confluence.
Created a new macro or looking for macros?
Share your macros and find new plugins on the Atlassian Plugin Exchange.
Need a simple macro? Consider a User Macro.
If you want to create a macro that just inserts some boiler-plate text or performs simple formatting, you may only need a
User Macro. User macros can be written entirely from within the Confluence web interface, and require no special
installation or programming knowledge.
Make your macro work in the Macro Browser.
Make your macro look good in the macro browser.
Looking for a tutorial?
Try this one: Tutorial on writing macros for Confluence.
Adding a macro plugin
Macros are a kind of Confluence plugin module.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins.
First steps: Creating a very basic plugin
Make sure you have created your first macro plugin using our description, How to Build an Atlassian Plugin. That will save you a lot of time.
The next step: Understanding a slightly more realistic macro plugin
The WoW plugin is a fun side-project created by Confluence developer Matthew Jensen. It loads information about World-of-Warcraft from a
remote server, renders it on a Confluence page, and uses JavaScript for a nice hover-effect. You should download the source and learn
more about it on the WoW Macro explanation page.
The Macro plugin module
Each macro is a plugin module of type "macro", packaged with whatever Java classes and other resources (i.e. Velocity templates) that the
macro requires in order to run. Generally, similar macros are packaged together into a single plugin, for ease of management. Here is an
example atlassian-plugin.xml file
<atlassian-plugin name='Task List Macros' key='confluence.extra.tasklist'>
<plugin-info>
<description>Macros to generate simple task lists</description>
<vendor name="Atlassian Software Systems" url="http://www.atlassian.com"/>
<version>1.3</version>
</plugin-info>
<macro name='tasklist' class='com.atlassian.confluence.extra.tasklist.TaskListMacro'
key='tasklist'>
<description>Creates a very simple task list, with user checkable tasks</description>
</macro>
<!-- more macros... -->
</atlassian-plugin>
The name of the macro defines how it will be referenced from the page. So if you define your macro as having name="tasklist", the
macro will be called from the page as {tasklist}.
The Macro plugin module implementing class
The class attribute of the macro defines what Java class will be used to process that macro. This is the class you need to write in order for
the macro to function. It must implement the com.atlassian.renderer.v2.macro.Macro interface.
A more complete guide to writing macros can be found in Writing Macros.
Using a Velocity Template
To use a Velocity template to provide the output of your macro, see Rendering Velocity templates in a macro.
Example macro plugins
The source-code of a number of macros (some of which are already built and packaged with Confluence) can be found in the plugins
directory of your Confluence distribution. You can modify these macros (consistent with the Confluence license). The most interesting macros
to read if you're looking at writing your own are probably:
tasklist – a simple macro that stores its state in a page's PropertySet
userlister – a macro that works in combination with an event listener to list logged-in users
livesearch – a macro that leverages Javascript and XMLHttpRequest in combination with an XWork plugin to handle the
server-side interaction.
graphviz – a macro that interacts with an external, non-Java tool
RELATED TOPICS
Plugin Tutorial - Writing Macros for Confluence
Anatomy of a simple but complete macro plugin
Documenting Macros
Including Information in your Macro for the Macro Browser
REV400 Including Information in your Macro for the Macro Browser
WoW Macro explanation
Writing Macros
Anatomy of a simple but complete macro plugin
This page is under construction
While most other documentation on this space refers to details of plugins and macro development, this page intends to focus on all aspects
of a simple (and fun!) plugin
The WoW macro plugin
The WoW macro shows you how to read a parameter from the page, to connect to an external webserver, retrieve some data, display the
data using our rendering engine Velocity, and how to hook up our JavaScript library JQuery into your plugin to generate a cool hover-over
effect.
To see the macro in action, please refer to our plugin demo space on our demo server, located at
http://confluence.demo.atlassian.com/display/PLUGINS/WoW-Macro
Sourcecode
The sourcecode can be found over here:
Important code snippets
Interesting parts of the code one by one:
Documenting Macros
This document applies to the notation guide only. Please also see Including Information in your Macro for the Macro Browser.
The Confluence notation guide is the popup window that describes all the markup and macros available within a Confluence installation.
Obviously, if a macro is installed, you will want it to also appear in the notation guide.
To do this you will need to:
1. Write a help file
2. Tell Confluence where to find that help file
Writing the Help File
The help file is a file containing a fragment of HTML. Your HTML will be inserted into a two-columned table, so you should provide a single
table row with two columns. On the left-hand side, put usage examples of your macro. On the right hand side provide a description and
sample output.
The file will be rendered through Velocity, which means useful things like $req.contextPath are available to you.
Here's an example of the help file used for the {note} macro:
<tr bgcolor=ffffff>
<!-- The left-hand table cell should contain usage examples -->
<td>
{note:title=Be Careful}<br />
The body of the note here..<br />
{note}
</td>
<!-- The right-hand cell describes the macro and its available arguments -->
<!-- and may include sample output -->
<td>
<p>
Prints a simple note to the user.
<!-- Provide a list of all possible macro arguments -->
<ul>
<li><b>title:</b> - (optional) the title of the note.</li>
<li><b>icon:</b> - (optional) if "false", don't display the icon.</li>
</ul>
</p>
<!-- This is the sample output -->
<div align='center'><div class='informationMacroPadding'>
<table cellpadding='5' width='85%' cellspacing='0' class='noteMacro' border='0'>
<tr><td width='16' valign='top'>
<img src="$req.contextPath/images/icons/emoticons/warning.png" width="16"
height="16" align="absmiddle" alt="" border="0"></td><td>
<b class="strong">Be Careful</b><br /><br/>
The body of the note here.. $req.contextPath/images/icons/emoticons/warning.png
</td></tr></table></div></div>
</td>
</tr>
Configuring the help file in your macro
The help file is included in your macro as a plugin resource of type "velocity" and name "help". Here's the plugin definition of the note macro,
including its help file:
<macro name='note' class='com.atlassian.confluence.extra.information.NoteMacro' key='note'>
<description>Draws a note (yellow).</description>
<resource type="velocity" name="help" location="templates/extra/layout/notemacro-help.vm">
<param name="help-section" value="advanced"/>
</resource>
</macro>
The "help-section" parameter is optional, and determines which section of the notation guide the macro will be documented in. The following
sections are available (Note that regular wiki markup is also defined in here, so some sections like 'breaks' are unlikely to be appropriate for
any real macro):
texteffects
Macros that change the appearance of text contained within them (e.g. {color})
headings
Macros that create headings within a page
breaks
Macros related to line- or paragraph breaks, or rulers
links
Macros related to links to other wiki or external content (e.g. {anchor})
lists
Macros related to lists
images
Macros for inserting or manipulating images within a page (e.g. {gallery})
tables
Macros for forming static tables (e.g. {section} and {column})
advanced
Macros for creating more complex structures in a page (e.g. {panel} or {info})
confluence
Macros for manipulating or displaying Confluence data (e.g. {children})
external
Macros for manipulating or displaying data from other systems (e.g. {rss})
misc
Macros that do anything else (Try to avoid using this section)
If you don't provide a help section, your macro documentation will appear in the "Macros" section of the notation guide. (This section only
appears in the notation guide if it is needed).
Including Information in your Macro for the Macro Browser
The Macro Browser is a new feature in Confluence 3.0, helping users to browse and insert macros while adding/editing content. If you are a
plugin author, you may want to include information in your macro so that it makes use of the new Macro Browser framework.
Default macro display
Even without including the specific Macro Browser information in your plugin, macros will be available in the Macro Browser's 'All' category.
As demonstrated in the screenshot below, the Vote macro is available with its description displayed.
The insert macro screen will then display:
a single input field for the parameters
body text field (only if the macro returns true for hasBody())
notation help for the macro (only if available)
Including Macro Browser Information in your Macro
However, you may want to include information in your macro so that it behaves correctly in the macro browser and displays the correct
parameter input fields. This will require simple additions to your macro definition in the atlassian-plugin.xml file.
Macro Descriptor Attributes
The following macro attributes contain information specifically for the macro browser.
Name
Description
Default
documentation-url
The absolute url to the macro's documentation.
icon
The relative url to the application for the macro icon. To display well in the macro browser, the image should
be 80 pixels by 80 pixels, with no transparency.
Note: if you have your icon defined as a downloadable resource, you can refer to this by specifying
"/download/resources/PLUGIN_KEY/RESOURCE_NAME" as the icon attribute.
hide-body
This attribute is available for macros that falsely declare that they have body (most likely cause they extend
BaseMacro) when they don't.
For example the gallery macro. This attribute helps hide the body text field in the macro browser.
false
hidden
If set to true, the macro is not visible in the macro browser for selection. Plugin authors may want to hide
macros that are for their plugin's internal use and shouldn't really be used by users. Note that the parameter
does not stop people from inserting a macro via the normal editor though.
false
New Macro Elements
The following macro elements contain information specifically for the macro browser. They should be placed inside your <macro> element.
Name
category
Required
Description
The category the macro should appear in. Valid categories are listed below.
alias
Defines an alias for the macro. This means that the macro browser will open for the defined aliases as if it were
this macro.
For example if dynamictasklist is an alias of tasklist, editing an existing dynamictasklist macro will
open it as a tasklist macro.
parameters
Defines a group of parameter elements. See example below.
parameter
This defines a single macro parameter. It must be an element of the parameters element. Its contents are
described below.
Macro Categories
The following categories for macros have been defined (see MacroCategory.java). A macro with no category will show up in the default 'All'
category.
formatting
confluence-content
visuals
navigation
external-content
communication
reporting
admin
development
Parameter Options
Each <parameter> element must have the following attributes:
name - A unique name of the parameter, or "" for the default (unnamed) parameter.
type - The type of parameter. Currently the following parameter types are supported in the macro browser's UI:
boolean - displays a check box
enum - displays a select field
string - displays an input field (this is the default if unknown type)
spacekey - displays an autocomplete field for search on space names
attachment - displays an autocomplete field for search on attachment filenames
username - displays an autocomplete field for search on username and full name
confluence-content - displays an autocomplete field for search on page and blog titles
These are optional:
required - whether it is a required parameter, defaults to 'false'
multiple - whether it takes multiple values, defaults to 'false'
default - the default value for the parameter
It can also have the following optional child elements:
<alias name="xxx"/> - alias for the macro parameter
<value name="xxx"/> - describes a single 'enum' value - only applicable for enum typed parameters
Example
The following is an example of the Recently Updated Macro defined:
<macro name="recently-updated" key="recently-updated"
icon="/images/icons/macrobrowser/recently-updated.png"
documentation-url="http://confluence.atlassian.com/display/DOC/Recently+Updated+Macro">
<category name="confluence-content"/>
<parameters>
<parameter name="spaces" type="spacekey" multiple="true">
<alias name="space"/>
</parameter>
<parameter name="labels" type="string">
<alias name="label"/>
</parameter>
<parameter name="width" type="percentage" default="100%"/>
<parameter name="types" type="string">
<alias name="type"/>
</parameter>
<parameter name="max" type="int" default="100">
<alias name="maxResults"/>
</parameter>
<parameter name="sort" type="enum">
<value name="title"/>
<value name="creation"/>
<value name="modified"/>
</parameter>
<parameter name="reverse" type="boolean" default="false"/>
</parameters>
</macro>
Note that this example contains parameter types which aren't all supported in the macro browser UI, but may be in future releases.
Macro Icon Example
To provide an icon for your macro 1) Create a resource for icons/images if you don't already have one. e.g.
<resource key="icons" name="icons/" type="download" location="myplugin/images/icons"/>
This must be a top level resource in your atlassian-plugin.xml and must be defined before the macro.
2) Ensure your plugin should contain the resource directory myplugin/images/icons
3) Set the icon attribute on the macro e.g.
icon="/download/resources/pluginkey/icons/iconfile.png"
i18n Conventions
Instead of having to define i18n keys for each element in the macro definition, the following convention is used to lookup i18n keys for the
macro browser.
Key
Description
{pluginKey}.{macroName}.label
Macro label/display name
{pluginKey}.{macroName}.desc
Macro description
{pluginKey}.{macroName}.param.{paramName}.label
Macro parameter label
{pluginKey}.{macroName}.param.{paramName}.desc
Macro parameter description
{pluginKey}.{macroName}.body.label
Macro body label (defaults to 'Body Text' if not provided)
{pluginKey}.{macroName}.body.desc
Macro body description
You will need to place the keys in a .properties file with a resource of type i18n in your plugin.
REV400 Including Information in your Macro for the Macro Browser
This page is a draft in progress and visible to atlassian-staff only.
The Macro Browser helps users to browse and insert macros while adding or editing content. If you are a plugin author, you need to include
metadata in your macro so that works correctly and makes use of the new Macro Browser framework.
As of Confluence 4.0, all macros must contain metadata in order to function correctly in Confluence.
Including Macro Browser Information in your Macro
You need to include information in your macro so that it appears and behaves correctly in the macro browser and displays the correct
parameter input fields. This will require simple additions to your macro definition in the atlassian-plugin.xml file.
Macro Descriptor Attributes
The following macro attributes contain information specifically for the macro browser.
Name
Description
Default
documentation-url
The absolute URL pointing to the macro's documentation.
icon
The relative URL to the application for the macro icon. To display well in the macro browser, the image
should be 80 pixels by 80 pixels, with no transparency.
Note: if you have your icon defined as a downloadable resource, you can refer to this by specifying
"/download/resources/PLUGIN_KEY/RESOURCE_NAME" as the icon attribute.
hide-body
This attribute is available for macros that falsely declare that they have body (most likely cause they extend
BaseMacro) when they don't.
For example the Gallery macro. This attribute helps hide the body text field in the macro browser.
false
hidden
If set to true, the macro is not visible in the macro browser for selection. Plugin authors may want to hide
macros that are for their plugin's internal use and shouldn't really be used by users.
false
Macro Elements
The following macro elements contain information specifically for the macro browser. They should be placed inside your <macro> element.
Name
Required
Description
category
The category the macro should appear in. Valid categories are listed below.
alias
Defines an alias for the macro. This means that the macro browser will open for the defined aliases as if it were
this macro.
For example if dynamictasklist is an alias of tasklist, editing an existing dynamictasklist macro will
open it as a tasklist macro.
parameters
Defines a group of parameter elements. See example below.
parameter
This defines a single macro parameter. It must be an element of the parameters element. Its contents are
described below.
Macro Categories
The following categories for macros have been defined (see MacroCategory.java). A macro with no category will show up in the default 'All'
category.
formatting
confluence-content
visuals
navigation
external-content
communication
reporting
admin
development
Parameter Options
Each <parameter> element must have the following attributes:
name - A unique name of the parameter, or "" for the default (unnamed) parameter.
type - The type of parameter. Currently the following parameter types are supported in the macro browser's UI:
boolean - displays a check box.
enum - displays a select field.
string - displays an input field (this is the default if unknown type).
spacekey - displays an autocomplete field for search on space names.
attachment - displays an autocomplete field for search on attachment filenames.
username - displays an autocomplete field for search on username and full name.
confluence-content - displays an autocomplete field for search on page and blog titles.
These are optional:
required - whether it is a required parameter, defaults to 'false'.
multiple - whether it takes multiple values, defaults to 'false'.
default - the default value for the parameter.
It can also have the following optional child elements:
<alias name="xxx"/> - alias for the macro parameter.
<value name="xxx"/> - describes a single 'enum' value - only applicable for enum typed parameters.
Example
The following is an example of the Recently Updated Macro defined:
<macro name="recently-updated" key="recently-updated"
icon="/images/icons/macrobrowser/recently-updated.png"
documentation-url="http://confluence.atlassian.com/display/DOC/Recently+Updated+Macro">
<category name="confluence-content"/>
<parameters>
<parameter name="spaces" type="spacekey" multiple="true">
<alias name="space"/>
</parameter>
<parameter name="labels" type="string">
<alias name="label"/>
</parameter>
<parameter name="width" type="percentage" default="100%"/>
<parameter name="types" type="string">
<alias name="type"/>
</parameter>
<parameter name="max" type="int" default="100">
<alias name="maxResults"/>
</parameter>
<parameter name="sort" type="enum">
<value name="title"/>
<value name="creation"/>
<value name="modified"/>
</parameter>
<parameter name="reverse" type="boolean" default="false"/>
</parameters>
</macro>
Note that this example contains parameter types which aren't all supported in the macro browser UI, but may be in future releases.
Macro Icon Example
To provide an icon for your macro 1) Create a resource for icons/images if you don't already have one. e.g.
<resource key="icons" name="icons/" type="download" location="myplugin/images/icons"/>
This must be a top level resource in your atlassian-plugin.xml and must be defined before the macro.
2) Ensure your plugin should contain the resource directory myplugin/images/icons
3) Set the icon attribute on the macro e.g.
icon="/download/resources/pluginkey/icons/iconfile.png"
i18n Conventions
Instead of having to define i18n keys for each element in the macro definition, the following convention is used to lookup i18n keys for the
macro browser.
Key
Description
{pluginKey}.{macroName}.label
Macro label/display name
{pluginKey}.{macroName}.desc
Macro description
{pluginKey}.{macroName}.param.{paramName}.label
Macro parameter label
{pluginKey}.{macroName}.param.{paramName}.desc
Macro parameter description
{pluginKey}.{macroName}.body.label
Macro body label (defaults to 'Body Text' if not provided)
{pluginKey}.{macroName}.body.desc
Macro body description
You will need to place the keys in a .properties file with a resource of type i18n in your plugin.
WoW Macro explanation
Overview
To flesh out the example macros, and to learn a bit about the
process myself, I wrote a macro to insert World of Warcraft item
links into any Confluence page. If you're not familiar with World of
Warcraft (or WoW for those in-the-know) it's a MMORPG with
millions of players world wide. What better way to show of some
CSS and JavaScript integration with Confluence!
First a quick overview of what the macro is trying to do. If you've
ever played the game or read any of the many WoW community
web sites you would be familiar with item links. Each item in
WoW has several properties that describe its use, its impossible
to memorize the thousands of different items, so these web sites
use javascript to add a popup to display item's details. When you move the mouse over the link the popup appears, showing you the details
of the item. This example shows a link to the Skullsplitter Helm.
The macro works by sending a message to Allakhazam's XML interface which is validated and parsed into an object. The Macro then uses a
velocity template to generate a small snippet of HTML and with some jQuery JavaScript wizardry, produces a popup.
The Plugin
The World of Warcraft plugin consists of two parts: The Macro, and The Web Resources.
The Macro
The heart of any macro is the execute method. This method is responsible for returning the result of the macro's function, either in HTML or
in WikiMarkup.
This macro goes through a very predictable process:
1. Validate and interpret the parameters
2. Connect to Allakhazam and ask for the item details
3. Use velocity to render the output
For the complete source take a look here.
Validate The Input
We have some very simple requirements for input. We only have one parameter which is the item id. To conform with what Allakhazam uses,
I chose to call this parameter witem. I also wanted to allow the user to supply the parameter without a name. The process to do this is
described briefly here.
String witemString = (String) params.get("0");
if (witemString == null)
{
witemString = (String) params.get("witem");
}
if (witemString == null)
{
return "No witem specified.";
}
int witem;
try
{
witem = Integer.parseInt(witemString);
}
catch (NumberFormatException e)
{
return "witem specified is not a number: "+witemString;
}
This code shows the process to check for the named and unnamed parameters (using the unnamed as preference). The string value is then
validated by trying to convert to an integer.
Connect to Allakhazam
Now that we have a valid integer, that is hopefully valid item id, we use the HttpRetrievalService to send a HTTP request to the
Allakhazam website.
HttpResponse response = httpRetrievalService.
get("http://wow.allakhazam.com/cluster/item-xml.pl?witem=" + witem);
if (response.getStatusCode() != 200)
{
return "error " + response.getStatusCode() + " loading item";
}
For the macro to have access to the HttpRetrievalService all we need is a setter method named appropriately. Confluence will call this
method at the appropriate time, you can do this to inject any of the Confluence components.
private HttpRetrievalService httpRetrievalService;
public void setHttpRetrievalService(HttpRetrievalService httpRetrievalService)
{
this.httpRetrievalService = httpRetrievalService;
}
The HttpRetrievalService takes care of the http connection for us and will time out according to the settings in the Administration
section of the Confluence system. The retrieval service will give us an InputStream as the result so we need to read that and create an
object which we can actually use.
The result from Allakhazam is in XML format, and its usually pretty small. I chose to use a DOM parser process the XML then a series of
XPath queries to extract the details we wanted:
DocumentBuilderFactory docFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = docFactory.newDocumentBuilder();
Document document = builder.parse(in);
XPathFactory xpathFactory = XPathFactory.newInstance();
XPath xpath = xpathFactory.newXPath();
final String nameString = (String) xpath.evaluate("//wowitem/name1", document,
XPathConstants.STRING);
final String htmlString = (String) xpath.evaluate("//wowitem/display_html", document,
XPathConstants.STRING);
final String qualityString = (String) xpath.evaluate("//wowitem/quality", document,
XPathConstants.STRING);
item.setName( StringUtils.isEmpty(nameString) ? UNKNOWN_ITEM : nameString);
item.setHtml( StringUtils.isEmpty(htmlString) ? "" : htmlString);
if (StringUtils.isNotEmpty(qualityString))
{
int quality = Integer.parseInt(qualityString);
item.setQuality(quality);
}
else
{
item.setQuality(0);
}
Allakhazam's XML format includes item details that match other locales, an extension to this plugin could use this information to provide the
popup in the current user's locale!
Render the Output
This macro uses velocity to render the output. This is helped using the VelocityUtils class which provides easy to use methods for
accessing the Velocity subsystem.
VelocityContext contextMap = new VelocityContext(MacroUtils.defaultVelocityContext());
contextMap.put(BODY_FIELD, item.getHtml());
contextMap.put(NAME_FIELD, item.getName());
contextMap.put(LINK_FIELD, "http://wow.allakhazam.com/db/item.html?witem=" + witem);
contextMap.put(QUALITY_FIELD, item.getQualityName());
return VelocityUtils.getRenderedTemplate(TEMPLATE_NAME, contextMap);
We first create a context map by calling MacroUtils.defaultVelocityContext()). This creates a Map of some useful components for
our template rendering. Creating a context like this is important if you want to access macro's and other components supplied by Confluence.
This example then places this map into a VeloctyContext object to provide type safety on the put methods.
The template used by this macro is extremely simple.
<!-#requireResource("confluence.web.resources:jquery")
#requireResource("com.atlassian.confluence.plugins.wow-plugin:resources")
-->
<div class="wow"><a class="wow-link wowitemtitle $qualityName"
href="$link">$itemName</a>$body</div>
The references to $qualityName, $link, $itemName, and $body are resolved by Velocity as the template is processed. They are looked up
in the context we supplied in the macro.
The two #requireResource calls tell Confluence to include the required resources in the page.
The first call tells Confluence to include jQuery. jQuery is actually available on every Confluence page, but since our macro uses it, we need
to make sure Confluence loads jQuery first. We do that by supplying an explicit dependency in the order in which we need it.
These resources are configured in the atlassian-plugin.xml file inside the plugin.
<web-resource key="resources" name="WoW Resources"
i18n-name-key="com.atlassian.confluence.plugins.wow-plugin.resources">
<resource type="download" name="wow.css" location="wow.css"/>
<resource type="download" name="wow.js" location="wow.js"/>
</web-resource>
This snippet of the configuration shows the definition of the resources this macro uses. Confluence will use the extension of the name
attribute to work out how to link in the resource (ie: link or script tag).
We have two resources, one for the CSS and one for our JavaScript.
Resources
The web resources configured in the previous section contain the CSS formating and JavaScript behavior of the macro. The CSS file is
simple enough and can be seen here. Most of this CSS was taken from the Allakhazam web site then customized to work within Confluence.
To do this I added a parent div tag to reduce the scope of the css selectors.
The JavaScript code uses jQuery to provide mouse over and popup functionality over the item link:
jQuery(function ($) {
$(".wow-link").hover(function () {
$(this).parent().find(".wowitem").show();
}, function () {
$(this).parent().find(".wowitem").hide();
});
$(".wow-link").mousemove(function(e) {
$(this).parent().find(".wowitem").css("left", e.pageX).css("top", e.pageY);
});
});
First, we a hook into the hover event on any element with the wow-link class. As the mouse enters over the link, the next sibling with the
class wowitem will be shown. As the mouse leaves, its hidden. This turns the item information section on an off.
Another hook is added on the mouse move event while the pointer is over the link. This hook is used to move the popup with the mouse
pointer.
The Result
You can download this plugin from here and install it through the Administration section of your 2.8.x Confluence instance.
The source is available here.
Compiling the Source
The general details of compiling a plugin applies to this plugin, so follow the instructions there.
For the impatient:
1.
2.
3.
4.
5.
Install JDK 1.5 or later
Install Maven 2
Create a settings.xml file (a good start is this one)
Checkout the trunk or a tag of the source
use maven to compile it: mvn clean package
Writing Macros
Macros are written and deployed into Confluence as macro plugin modules. This page describes how to write a macro (but not how to get the
plugin working, refer to the other page for that).
First steps
Make sure you have created your first macro plugin using our guide, How to Build an Atlassian Plugin. That will save you a lot of time.
The Macro Class
All macros must implement the com.atlassian.renderer.v2.macro.Macro interface. The Javadoc comments are probably the best
place to start:
http://docs.atlassian.com/atlassian-renderer/latest/apidocs/index.html?com/atlassian/renderer/v2/macro/Macro.html
The BaseMacro class
While it's not a requirement, your macro should extend the com.atlassian.renderer.v2.macro.BaseMacro abstract
class. This class does not contain any functionality, but if the Macro interface changes in the future, the BaseMacro class
will be maintained in order to ensure backwards compatibility with existing macros.
Writing Your Macro
When writing a macro, you will need to override the following methods:
Method
Should return...
hasBody()
true if this macro expects to have a body, false otherwise
getBodyRenderMode()
The RenderMode under which the body should be processed before being passed into the macro
isInline()
false if the macro produces a block element (like a paragraph, table or div) true if it is inline and should be
incorporated into surrounding paragraphs
execute()
a fragment of HTML that is the rendered macro contents
Understanding RenderMode
The RenderMode tells the Confluence wiki renderer which wiki-conversion rules should be applied to a piece of text. Once again, the best
place to start is the Javadoc:
http://docs.atlassian.com/atlassian-renderer/latest/apidocs/index.html?com/atlassian/renderer/v2/RenderMode.html
There are a number of pre-defined render modes. The ones that would be useful to macro writers are probably:
Mode
Description
RenderMode.ALL
Render everything
RenderMode.NO_RENDER
Don't render anything: just return the raw wiki text
RenderMode.INLINE
Render things you'd normally find inside a paragraph, like links, text effects and so on
RenderMode.SIMPLE_TEXT
Render text made up only of paragraphs, without images or links
If you want finer control, RenderMode is implemented as a bit-field. Each constant of RenderMode starting with F_ is a feature of the
renderer that can be turned on or off. You can construct a RenderMode by manipulating these bits through the following methods:
Method
Description
Example
RenderMode.allow()
Allow only
the
renderings
specified
RenderMode.allow(RenderMode.F_LINKS || RenderMode.F_HTMLESCAPE) will only
render links, and escape HTML entities.
RenderMode.suppress()
Allow all
renderings
except
those
specified
RenderMode.suppress(RenderMode.F_MACROS || RenderMode.F_TEMPLATE) will
render everything except macros and template variables
and()
Perform a
logical AND
on an
existing
render
mode
RenderMode.SIMPLE_TEXT.and(RenderMode.suppress(RenderMode.F_PARAGRAPHS))
will render SIMPLE_TEXT without paragraphs
or()
Perform a
logical OR
on an
existing
render
mode
RenderMode.SIMPLE_TEXT.and(RenderMode.allow(RenderMode.F_MACROS)) will
render SIMPLE_TEXT with macros
Many macros (like this note macro) produce a div. Often, if there's only one line of text within a div, you don't want it
surrounded in paragraph tags. For this reason, the RenderMode.F_FIRST_PARA flag controls the first line of wiki text that
is rendered. If F_FIRST_PARA is not set, and the first line of text is a paragraph, the paragraph tags will not be rendered.
How to determine the context your macro is being rendered in
One of the parameters to the execute() method, the one with type RenderContext, can be used to determine how the macro is being
rendered. See the relevant Confluence Developer FAQ entry for the details.
Accessing the Rest of the System
Like all Confluence plugin modules, Macros are autowired by the Spring framework. To obtain a manager object through which you can
interact with Confluence itself, all you need to do is provide a Javabeans-style setter method for that component on your Macro class. See
Accessing Confluence Components from Plugin Modules
Advanced Macro Techniques
Macros are often most powerful when combined with other plugin modules. For example, the {livesearch} macro uses an XWork plugin
module to perform its server-side duties, and the {userlister} plugin uses a listener plugin module to listen for login events and determine who
is online. You may also consider using a component plugin module to share common code or state between macros.
How Macros are Processed
If you want to know exactly what happens when a macro is processed, the following (slightly overly-detailed) description should help:
Consider the following code in a Wiki page:
{mymacro:blah|width=10|height=20}This _is_ my macro body{mymacro}
1. The MacroRendererComponent finds the first {mymacro:blah|width=10|height=20} tag, and asks the MacroManager if a
macro is currently active with the name "mymacro". The MacroManager returns a singleton instance of your Macro.
2. The MacroRendererComponent calls hasBody() on the Macro.
a. If hasBody() returns false, the macro is processed with a 'null' body, and the next {mymacro} tag will be processed as a
separate macro.
b. If hasBody() returns true, the MacroRendererComponent looks for the closing {mymacro}. Anything between the two
becomes the macro body.
i. If there is a macro body, the MacroRendererComponent then calls getRenderMode() on the macro to
determine how that body should be rendered
ii. The macro body is processed through the wiki renderer with the given RenderMode before being passed to the
macro
3. The MacroRendererComponent calls execute on the macro, passing in the macro parameters, the (processed) body, and the
current RenderMode
The execute method should return an HTML string. No further wiki processing is performed on macro output.
The parameters are a Map of {{String}}s, keyed by parameter name.
If any parameter is not named, it is keyed by the string representation of its position: so for the above example,
parameters.get("0") would return "blah".
parameters.get(Macro.RAW_PARAMS_KEY) will return the raw parameter string, in this case:
"blah|width=10|height=20"
4. The MacroRendererComponent calls isInline() on the macro to determine if its results should be inserted into the surrounding
page as an inline (i.e. part of a surrounding paragraph) or a block element.
RELATED TOPICS
Plugin Tutorial - Writing Macros for Confluence
Macro Module
Anatomy of a simple but complete macro plugin
Documenting Macros
Including Information in your Macro for the Macro Browser
REV400 Including Information in your Macro for the Macro Browser
WoW Macro explanation
Writing Macros
Module Type Module
Available:
Confluence 2.10 and later
Purpose of this Module Type
Module Type plugin modules allow you to dynamically add new plugin module types to the plugin framework, generally building on other
plugin modules. For example, a plugin developer could create a <dictionary> plugin module that is used to feed a dictionary service used
by still other plugins.
Configuration
The root element for the Module Type plugin module is module-type. It allows the following attributes and child elements for configuration:
Attributes
Name
Required
Description
class
The ModuleDescriptor class to instantiate when a new plugin module of this type is found. See the
plugin framework guide to creating plugin module instances.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
Default
false
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred. I.e. the identifier of the module type. This
value will be used as the XML element name to match.
name
The human-readable name of the plugin module.
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
Elements
Name
Required
description
Description
Default
The description of the plugin module. The 'key' attribute can be specified to declare a localisation key
for the value instead of text in the element body.
Example
Here is an example atlassian-plugin.xml file containing a plugin module type:
Our dictionary module descriptor allows plugins to provide dictionaries that get definitions of technical terms and phrases in various
languages. We have a Dictionary interface that looks like this:
The Java code for DictionaryModuleDescriptor could look like this:
This will add the new module type 'dictionary' to the plugin framework, allowing other plugins to use the new module type. Here is a plugin
that uses the new 'dictionary' module type:
Accessing modules of your dynamic module type can be done using com.atlassian.plugin.PluginAccessor.
Note that it is not advisable to cache the results of calls to com.atlassian.plugin.PluginAccessor's methods, since the return values
can change at any time as a result of plugins being installed, uninstalled, enabled, or disabled.
Notes
Some information to be aware of when developing or configuring a Module Type plugin module:
Not all dynamic module types will need to use the class attribute on the modules that implement them. For example, if the above
dictionary example just used a resource file to translate terms, and not an interface that plugins had to implement, plugins using the
dictionary module type might look like this:
The plugin that defines a new module type cannot use the module type in the Plugin Framework 2.1, but can in 2.2 or later.
If you want to have control over the construction of the ModuleDescriptor, you can skip the 'module-type' module and make public a
component registered against the ModuleDescriptorFactory interface:
Ensure your ModuleDescriptorFactory implements
com.atlassian.plugin.osgi.external.ListableModuleDescriptorFactory.
By specifying the application attribute in your module type element, you can ensure that a plugin only uses that module type
when it is running in a specific application. For example, with the following snippet, the dictionary module type is only used when the
plugin is loaded in JIRA:
The supported values for application are the Product Keys listed in the Atlassian Plugin SDK documentation.
RELATED TOPICS
Writing Confluence Plugins
Installing a Plugin
Information sourced from Plugin Framework documentation
Path Converter Module
Available:
Confluence 2.8 and later
Path Converter plugin modules are useful for developers who are writing Confluence plugins. The Path Converter modules allow you to
install custom path mapping as a part of your plugin.
For more information about plugins in general, read the Confluence Plugin Guide.
To learn how to install and configure plugins and macros, read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins .
The Path Converter Plugin Module
Each path converter can be used to map a friendly URL to an action either within Confluence or provided by your plugin. Here is an example
atlassian-plugin.xml file containing a single path converter:
<atlassian-plugin name="Hello World Converter" key="confluence.extra.simpleconverter">
<plugin-info>
<description>Example Path Converter</description>
<vendor name="Atlassian Software Systems" url="http://www.atlassian.com"/>
<version>1.0</version>
</plugin-info>
<path-converter weight="10" key="example-converter" class="plugin.ExamplePathConverter"/>
</atlassian-plugin>
The class attribute specifies a class that implements
com.atlassian.confluence.servlet.simpledisplay.PathConverter.
The weight element defines when the path converter is executed in relation to the other converters. See DOC:Notes below.
The key is the normal plugin module identifier.
Example
This converter is used by Confluence to convert the friendly /display/SpaceKey/PageTitle/ URL format into a request against the
correct WebWork action.
public class PagePathConverter implements PathConverter
{
private static final String DISPLAY_PAGE_PATH =
"/pages/viewpage.action?spaceKey=${spaceKey}&title=${title}";
public boolean handles(String simplePath)
{
return new StringTokenizer(simplePath, "/").countTokens() == 2;
}
public ConvertedPath getPath(String simplePath)
{
StringTokenizer st = new StringTokenizer(simplePath, "/");
String spaceKey = st.nextToken();
String pageTitle = st.nextToken();
ConvertedPath path = new ConvertedPath(DISPLAY_PAGE_PATH);
path.addParameter("spaceKey",spaceKey);
path.addParameter("title",pageTitle);
return path;
}
}
The handles method is called (in order of weight) on each converter and the first to return true will have its getPath method called.
The getPath method will convert the friendly path into a ConvertedPath object.
The ConvertedPath object expects a URL template and a series of parameters. It uses Velocity internally to merge the template and the
parameters, producing a final URL.
Notes
The com.atlassian.confluence.servlet.simpledisplay.SimpleDisplayServlet will pass each incoming request that starts
with '/display' to each of its configured converters. The converters will be checked starting with the converter with the lowest weight.
The PathConverters are autowired at registration time, so add a setter method on your converter to get access to Confluence's components.
public class MyPathConverter implements PathConverter
private PageManager pageManager;
// other methods
public void setPageManager(PageManager pageManager)
{
this.pageManager = pageManager;
}
}
Core Converters
Confluence includes a series of core path converters that are used to provide friendly URLs for standard Confluence resources.
Weight
Core PathConverter
10
com.atlassian.confluence.servlet.simpledisplay.PagePathConverter
20
com.atlassian.confluence.servlet.simpledisplay.MailPathConverter
30
com.atlassian.confluence.servlet.simpledisplay.BlogPathConverter
40
com.atlassian.confluence.servlet.simpledisplay.UserPathConverter
50
com.atlassian.confluence.servlet.simpledisplay.SpacePathConverter
You can use any weight for your converter. If the same weight is used by multiple converters, they are executed in order of registration.
Renderer Component Module
Renderer Component plugin modules are available in Confluence 2.8 and later.
Renderer Component plugins allow you to add additional processors when converting wiki markup to HTML.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
Warning
This document concerns plugging renderer components into Confluence. The implementation of renderer components themselves is not
documented.
Renderer component plugins were added to Confluence to make certain development tasks easier inside Atlassian. They are documented
here for Atlassian developers, and for the sake of completeness, but we do not recommend customers add their own plugins to this area.
The wiki markup rendering process is quite fragile, and simple changes can have wide-reaching effects.
In other words, you're on your own here.
Defining a Renderer Component Plugin
Here's a sample atlassian-plugin.xml fragment:
<renderer-component key="foo" name="Foo Renderer" class="com.example.frobozz.FooRendererComponent"
weight="1000">
<description>Convert foo markup to HTML</description>
</renderer-component>
The key is the required plugin module key, which must be unique within the plugin.
The name is the required display name for the plugin.
The class is the required class name for the renderer component implementation.
The weight number is required, and determines the order in which renderer component plugins are run over the wiki markup.
Components are run from lowest to highest weights
Implementing a Renderer Component Plugin
The class referred to by the module descriptor must implement one of the following interfaces:
com.atlassian.renderer.v2.components.RendererComponent
com.atlassian.renderer.v2.plugin.RendererComponentFactory
This allows you to provide either a component directly, or a factory that can be used to instantiate more complex components. If you are
using a factory, you can provide arbitrary parameters that will be passed to the factory when the component is instantiated:
<renderer-component key="foo" name="Foo Renderer"
class="com.example.frobozz.FooRendererComponentFactory" weight="1000">
<param name="animal">monkey</param>
<param name="vegetable">lettuce</param>
<param name="mineral">quartz</param>
</renderer-component>
These parameters will be passed into the factory's instantiate method as a Map<String, String>.
REST Module
The REST module is available only for OSGi-based plugins in Confluence 3.1 or later and requires version 2.2 or later of
the Atlassian Plugin Framework.
The REST plugin module allows plugin developers to create their own REST API for Confluence.
This module type is shared with other Atlassian products. See the common REST Plugin Module documentation for details.
RPC Module
Available:
Confluence 1.4 and later
RPC plugins allow you to deploy arbitrary SOAP or XML-RPC services within Confluence. These services may be completely independent of
Confluence, or may take advantage of the Confluence APIs to provide a remote, programmatic interface to the Confluence server.
Confluence's packaged remote API is implemented entirely as a plugin.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
The Remote API packaged with Confluence is documented at Confluence XML-RPC and SOAP APIs
XML-RPC Plugins
Here is an example atlassian-plugin.xml file containing a single XML-RPC service:
<atlassian-plugin name="Sample XML-RPC" key="confluence.extra.xmlrpc">
...
<rpc-xmlrpc key="helloworld-xmlrpc"
name="Hello World XML-RPC"
class="com.atlassian.confluence.extra.helloworldrpc.HelloWorld">
<description>A public example XML-RPC service</description>
<service-path>helloworld</service-path>
</rpc-xmlrpc>
...
</atlassian-plugin>
the class attribute defines the class that will be servicing XML-RPC requests. One instance of this class will be instantiated, and all
of its public methods will be made available remotely. The instance is autowired from the Spring context.
the service-path attribute is the method-prefix that is used to determine which XML-RPC method calls are routed to this plugin.
Confluence listens for XML-RPC requests at a single end-point. If your server is deployed at http://www.example.com then all XML-RPC
requests must be made to http://www.example.com/rpc/xmlrpc. As such, the service-path is used to distinguish which plugin each request is
directed at. If your RPC implementing class has a method provideGreeting(), and a service-prefix of helloworld, then the XML-RPC
method call will be helloworld.provideGreeting().
XML-RPC Interfaces
The XML-RPC specification is more limited than Java code. in particular:
all method parameters in the class you have deployed must take as arguments, and return as values only the
"XML-RPC-friendly types" listed below
null is not a valid XML-RPC type, so you must never send null as an argument, or return null as a value
void is not a valid XML-RPC return type, so all methods exposed via XML-RPC must return some value
Valid types for use as arguments in methods exposed via XML-RPC, or as return values from XML-RPC methods are:
int
boolean
java.lang.String
double
java.util.Date
java.util.Hashtable
java.util.Vector
byte[]
The object wrappers for the primitive types (java.lang.Integer, java.lang.Boolean, etc) may be used as return values, but not as
method arguments. For more information, see: http://ws.apache.org/xmlrpc/types.html
SOAP Plugins
Here is an example atlassian-plugin.xml file containing a single SOAP service:
<atlassian-plugin name="Sample XML-RPC" key="confluence.extra.xmlrpc">
...
<rpc-soap key="helloworld-soap" name="Hello World SOAP"
class="com.atlassian.confluence.extra.helloworldrpc.HelloWorld">
<description>A public example SOAP service</description>
<service-name>HelloWorldPublic</service-name>
<service-path>helloworld</service-path>
<published-interface>com.atlassian.confluence.extra.helloworldrpc.HelloWorldPublic</published-interface>
</rpc-soap>
...
</atlassian-plugin>
the class attribute defines the class that will be servicing SOAP requests. One instance of this class is instantiated and autowired
from the Spring context.
the service-path element defines the SOAP service-path for this plugin, and where its WSDL file will be located.
the published-interface element defines a Java interface that will be exposed via the SOAP service. The class defined in the class
attribute must implement this interface.
Confluence listens for SOAP requests at a single end-point. If your server is deployed at http://www.example.com then all XML-RPC
requests must be made to http://www.example.com/rpc/soap. The preferred method for calling a SOAP service on Confluence is by parsing
the Axis WSDL file that is generated automatically for any deployed SOAP plugin. If your plugin has a service-path of helloworld, its
WSDL file will be available at http://www.example.com/rpc/soap-axis/helloworld?WSDL
Unlike XML-RPC, SOAP can accept and return complex types.
RPC Authentication
Confluence supplies a very simple, token-based authentication service for its remote API. Users log in over the remote interface using a
login(username, password) method, and are supplied with a String token. This String token is then supplied as the first argument of
any subsequent remote call, to authenticate the user with their previous login. More information about this protocol can be found in the
Confluence XML-RPC and SOAP APIs documentation.
Any RPC plugin can take advantage of the authentication service. To do so you must make some changes to your remote service objects,
and to the configuration.
Here is an atlassian-plugin.xml containing SOAP and XML-RPC services that require authentication:
<atlassian-plugin name="Sample XML-RPC" key="confluence.extra.xmlrpc">
...
<rpc-xmlrpc key="helloworldsecure-xmlrpc"
name="Secure Hello World XML-RPC"
class="com.atlassian.confluence.extra.helloworldrpc.HelloWorldSecureImpl">
<description>An example XML-RPC service that requires a login</description>
<service-name>HelloWorldSecure</service-name>
<service-path>helloworld-secure</service-path>
<published-interface>com.atlassian.confluence.extra.helloworldrpc.HelloWorldSecure</published-interface>
<authenticate>true</authenticate>
</rpc-xmlrpc>
<rpc-soap key="helloworldsecure-soap"
name="Secure Hello World SOAP"
class="com.atlassian.confluence.extra.helloworldrpc.HelloWorldSecureImpl">
<description>An example SOAP service that requires a login</description>
<service-name>HelloWorldSecure</service-name>
<service-path>helloworld-secure</service-path>
<published-interface>com.atlassian.confluence.extra.helloworldrpc.HelloWorldSecure</published-interface>
<authenticate>true</authenticate>
</rpc-soap>
...
</atlassian-plugin>
An authenticated XML-RPC service requires an additional published-interface element that behaves like the published-interface element
in the SOAP plugin: you must supply a Java Interface to represent which methods of your plugin class are being exposed remotely. The
class represented by the class attribute must implement this interface.
There are two changes you have to make to your remote service objects (and their published interfaces) to allow them to take advantage of
authentication:
1. You must implement the String login(String username, String password) and boolean logout(String token)
methods in com.atlassian.confluence.rpc.SecureRpc. However, since these methods will be intercepted by the
Confluence RPC framework, they will never actually be called on your object. As such, you can leave the implementations empty.
2. All methods in your published interface must have an initial argument that is a String (the authentication token). This token will also
be intercepted by the Confluence RPC framework. Your code must not rely on this token having any value by the time the method is
called on your plugin.
If you are providing an authenticated service, the logged-in User will be available to you from
com.atlassian.confluence.user.AuthenticatedUserThreadLocal.getUser()
If anonymous RPC is enabled for your server, the logged-in user may be null
Hibernate Session
If you use the Confluence API within your plugin you will probably need to create a Hibernate session, and start a transaction. Getting an
error like: net.sf.hibernate.HibernateException: Could not initialize proxy - the owning Session was closed is
one indication. As a version 2 plugin does not have access to the transactionManager anymore, the SAL TransactionTemplate can be
used instead.
Using a simple HelloWorld example:
package com.atlassian.confluence.extra.helloworldrpcv2;
public interface HelloWorld
{
String sayHello();
}
this is a simple implementation with a programmatic transaction using the SAL TransactionTemplate.
package com.atlassian.confluence.extra.helloworldrpcv2;
import com.atlassian.confluence.spaces.SpaceManager;
import com.atlassian.sal.api.transaction.TransactionCallback;
import com.atlassian.sal.api.transaction.TransactionTemplate;
public class DefaultHelloWorld implements HelloWorld
{
private final TransactionTemplate transactionTemplate;
private final SpaceManager spaceManager;
public DefaultHelloWorld(final SpaceManager spaceManager, final TransactionTemplate
transactionTemplate)
{
this.spaceManager = spaceManager;
this.transactionTemplate = transactionTemplate;
}
public String sayHello()
{
return (String) transactionTemplate.execute(new TransactionCallback()
{
public Object doInTransaction()
{
return String.format("Hello world! Number of spaces: %d",
spaceManager.getAllSpaces().size());
}
});
}
}
In order to use the TransactionTemplate a component-import module needs to be added to the plugin descriptor:
<rpc-xmlrpc key="helloworld-xmlrpc"
name="Hello World XML-RPC"
class="com.atlassian.confluence.extra.helloworldrpcv2.DefaultHelloWorld">
<description>A public example XML-RPC service</description>
<service-path>helloworld</service-path>
</rpc-xmlrpc>
<component-import key="transactionTemplate">
<description>Import the
com.atlassian.sal.api.transaction.TransactionTemplate</description>
<interface>com.atlassian.sal.api.transaction.TransactionTemplate</interface>
</component-import>
and the dependency to the project's pom:
<dependency>
<groupId>com.atlassian.sal</groupId>
<artifactId>sal-api</artifactId>
<version>${sal.version}</version>
<scope>provided</scope>
</dependency>
[...]
<properties>
<sal.version>2.0.11</sal.version>
<confluence.version>3.1.1</confluence.version>
<confluence.data.version>3.1</confluence.data.version>
</properties>
<scm>
Instead of using the TransactionTemplate directly please consider using a DynamicProxy or Spring AOP to wrap your business logic inside a
transaction using the SAL TransactionTemplate.
Example
Example XML-RPC and SOAP plugins are available in the Confluence distribution under plugins/helloworldrpc.
It can also be[found here.
The full source to the Confluence remote API plugin can be found in the Confluence distribution under plugins/confluencerpc. The
Confluence Remote API uses a mixture of RPC plugins and Component Module, along with a simple mechanism to serialise Java objects
into an XML-RPC compatible struct, to serve the same API over both SOAP and XML-RPC. We strongly recommend you use a similar
mechanism to provide both RPC APIs.
Servlet Context Listener Module
Available:
Confluence 2.10 and later
Purpose of this Module Type
Servlet Context Listener plugin modules allow you to deploy Java Servlet context listeners as a part of your plugin. This helps you to
integrate easily with frameworks that use context listeners for initialisation.
Configuration
The root element for the Servlet Context Listener plugin module is servlet-context-listener. It allows the following attributes and
child elements for configuration:
Attributes
Name
Required
Description
class
The class which implements this plugin module. The class you need to provide depends on the
module type. For example, Confluence theme, layout and colour-scheme modules can use classes
already provided in Confluence. So you can write a theme-plugin without any Java code. But for
macro and listener modules you need to write your own implementing class and include it in your
plugin. See the plugin framework guide to creating plugin module instances. The servlet context
listener Java class. Must implement javax.servlet.ServletContextListener.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
Default
false
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred. I.e. the identifier of the context listener.
name
The human-readable name of the plugin module. I.e. the human-readable name of the listener.
The
plugin
key
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
Elements
Name
Required
description
Description
Default
The description of the plugin module. The 'key' attribute can be specified to declare a localisation key
for the value instead of text in the element body. I.e. the description of the listener.
Example
Here is an example atlassian-plugin.xml file containing a single servlet context listener:
Notes
Some information to be aware of when developing or configuring a Servlet Context Listener plugin module:
The servlet context you listen for will not be created on web application startup. Instead, it will be created the first time a servlet or
filter in your plugin is accessed after each time it is enabled, triggering a new instance of your listener followed by the calling of the
listener's contextCreated() method. This means that if you disable a plugin containing a listener and re-enable it again, the
following will happen:
1. The contextDestroyed() method will be called on your listener after the plugin was disabled.
2.
2. A new servlet context will be created after the plugin was re-enabled.
3. Your listener will be instantiated.
4. The method contextCreated() on your listener will be called.
RELATED TOPICS
Writing Confluence Plugins
Installing a Plugin
Information sourced from Plugin Framework documentation
Servlet Context Parameter Module
Available:
Confluence 2.10 and later
Purpose of this Module Type
Servlet Context Parameter plugin modules allow you to set parameters in the Java Servlet context shared by your plugin's servlets, filters,
and listeners.
Configuration
The root element for the Servlet Context Parameter plugin module is servlet-context-param. It allows the following attributes and child
elements for configuration:
Attributes
Name
Required
Description
class
The class which implements this plugin module. The class you need to provide depends on the
module type. For example, Confluence theme, layout and colour-scheme modules can use classes
already provided in Confluence. So you can write a theme-plugin without any Java code. But for
macro and listener modules you need to write your own implementing class and include it in your
plugin. See the plugin framework guide to creating plugin module instances.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
Default
false
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred. I.e. The identifier of the context
parameter.
name
The human-readable name of the plugin module. I.e. The human-readable name of the context
parameter.
The
plugin
key
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
Elements
Name
Required
Description
Default
description
The description of the plugin module. The 'key' attribute can be specified to declare a localisation key
for the value instead of text in the element body. I.e. the description of the listener.
param-name
The servlet context parameter name.
N/A
param-value
The servlet context parameter value.
N/A
Example
Here is an example atlassian-plugin.xml file containing a single servlet context parameter:
Notes
Some information to be aware of when developing or configuring a Servlet Context Parameter plugin module:
This parameter will only be available to servlets, filters, and context listeners within your plugin.
RELATED TOPICS
Writing Confluence Plugins
Installing a Plugin
Information sourced from Plugin Framework documentation
Servlet Filter Module
Available:
Confluence 2.10 and later
Purpose of this Module Type
Servlet Filter plugin modules allow you to deploy Java Servlet filters as a part of your plugin, specifying the location and ordering of your filter.
This allows you to build filters that can tackle tasks like profiling and monitoring as well as content generation.
Configuration
The root element for the Servlet Filter plugin module is servlet-filter. It allows the following attributes and child elements for
configuration:
Attributes
Name
Required
Description
class
The class which implements this plugin module. The class you need to provide depends on
the module type. For example, Confluence theme, layout and colour-scheme modules can
use classes already provided in Confluence. So you can write a theme-plugin without any
Java code. But for macro and listener modules you need to write your own implementing
class and include it in your plugin. See the plugin framework guide to creating plugin
module instances. The servlet filter Java class must implement javax.servlet.Filter.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled
by default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is
defined.
Default
false
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with
the complete module key. A module with key fred in a plugin with key
com.example.modules will have a complete key of com.example.modules:fred. I.e.
the identifier of the servlet filter.
location
The position of the filter in the application's filter chain. If two plugins provide filters at the
same position, the 'weight' attribute (see below) is evaluated.
before-dispatch
after-encoding - Near the very top of the filter chain in the application, but after
any filters which ensure the integrity of the request.
before-login - Before the filter that logs in the user with any authentication
information included in the request.
before-decoration - Before the filter which does decoration of the response,
typically with Sitemesh.
before-dispatch - At the end of the filter chain, before any servlet or filter which
handles the request by default.
name
The human-readable name of the plugin module. I.e. the human-readable name of the
filter.
The plugin key
system
Indicates whether this plugin module is a system plugin module (value='true') or not
(value='false'). Only available for non-OSGi plugins.
false
weight
The weight of the filter, used to decide which order to place the filter in the chain for filters
which have specified the same 'location' attribute (see above). The higher weight, the lower
the filter's position.
100
Elements
Name
Required
Description
Default
description
The description of the plugin module. The 'key' attribute can be specified to declare a
localisation key for the value instead of text in the element body. I.e. the description of the
filter.
init-param
Initialisation parameters for the filter, specified using param-name and param-value
sub-elements, just as in web.xml. This element and its child elements may be repeated.
N/A
resource
A resource for this plugin module. This element may be repeated. A 'resource' is a
non-Java file that a plugin may need in order to operate. Refer to Adding Plugin and
Module Resources for details on defining a resource.
N/A
url-pattern
The pattern of the URL to match. This element may be repeated.
N/A
The URL pattern format is used in Atlassian plugin types to map them to URLs. On the
whole, the pattern rules are consistent with those defined in the Servlet 2.3 API. The
following wildcards are supported:
* matches zero or many characters, including directory slashes
? matches zero or one character
Examples
/mydir/* matches /mydir/myfile.xml
/*/admin/*.??ml matches /mydir/otherdir/admin/myfile.html
dispatcher
Determines when the filter is triggered. You can include multiple dispatcher elements.
If this element is present, its content must be one of the following, corresponding to the
filter dispatcher options defined in the Java Servlet 2.4 specification:
Filter will be
triggered only for
requests from the
client
REQUEST: the filter applies to requests that came directly from the client
INCLUDE: the filter applies to server-side includes done via
RequestDispatcher.include()
FORWARD: the filter applies to requests that are forwarded via
RequestDispatcher.forward()
ERROR: the filter applies to requests that are handled by an error page
Note: This element is only available in Plugin Framework 2.5 and later.
If this element is not present, the default is REQUEST. (This is also the behaviour
for Plugin Framework releases earlier than 2.5.)
Example
Here is an example atlassian-plugin.xml file containing a single servlet filter:
Accessing your Servlet Filter
Your servlet will be accessible within the Atlassian web application via each url-pattern you specify, but unlike the Servlet Plugin Module,
the url-pattern is relative to the root of the web application.
For example, if you specify a url-pattern of /helloworld as above, and your Atlassian application was deployed at http://yourserver/jira
— then your servlet filter would be accessed at http://yourserver/jira/helloworld .
Notes
Some information to be aware of when developing or configuring a Servlet Filter plugin module:
Your servlet filter's init() method will not be called on web application startup, as for a normal filter. Instead, this method will be
called the first time your filter is accessed after each time it is enabled. This means that if you disable a plugin containing a filter or a
single servlet filter module, and re-enable it again, the filter will be re-created and its init() method will be called again.
Because servlet filters are deployed beneath root, be careful when choosing each url-pattern under which your filter is deployed.
If you plan to handle the request in the filter, it is recommended to use a value that will always be unique to the world!
Some application servers, like WebSphere 6.1, won't call servlet filters if there is no underlying servlet to match the URL. On these
systems, you will only be able to create a filter to handle normal application URLs.
RELATED TOPICS
Writing Confluence Plugins
Installing a Plugin
Information sourced from Plugin Framework documentation
Servlet Module
Available:
Confluence 1.4 and later
Purpose of this Module Type
Servlet plugin modules enable you to deploy Java servlets as a part of your plugins.
Configuration
The root element for the Servlet plugin module is servlet. It allows the following attributes and child elements for configuration:
Attributes
Name
Required
Description
class
The servlet Java class. Must be a subclass of javax.servlet.http.HttpServlet. See the
plugin framework guide to creating plugin module instances.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
Default
false
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred. I.e. the identifier of the servlet.
name
The human-readable name of the plugin module. I.e. the human-readable name of the servlet.
The
plugin
key.
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
Elements
Name
Required
Description
Default
description
The description of the plugin module. The 'key' attribute can be specified to declare a localisation key
for the value instead of text in the element body. I.e. the description of the servlet.
init-param
Initialisation parameters for the servlet, specified using param-name and param-value sub-elements,
just as in web.xml. This element and its child elements may be repeated.
N/A
resource
A resource for this plugin module. This element may be repeated. A 'resource' is a non-Java file that a
plugin may need in order to operate. Refer to Adding Plugin and Module Resources for details on
defining a resource.
N/A
url-pattern
The pattern of the URL to match. This element may be repeated.
N/A
The URL pattern format is used in Atlassian plugin types to map them to URLs. On the whole, the
pattern rules are consistent with those defined in the Servlet 2.3 API. The following wildcards are
supported:
* matches zero or many characters, including directory slashes
? matches zero or one character
Examples
/mydir/* matches /mydir/myfile.xml
/*/admin/*.??ml matches /mydir/otherdir/admin/myfile.html
Example
Here is an example atlassian-plugin.xml file containing a single servlet:
Accessing your Servlet
Your servlet will be accessible within the Atlassian web application via each url-pattern you specify, beneath the /plugins/servlet
parent path.
For example, if you specify a url-pattern of /helloworld as above, and your Atlassian application was deployed at http://yourserver/jira
— then your servlet would be accessed at http://yourserver/jira/plugins/servlet/helloworld .
Notes
Some information to be aware of when developing or configuring a Servlet plugin module:
Your servlet's init() method will not be called on web application startup, as for a normal servlet. Instead, this method will be
called the first time your servlet is accessed after each time it is enabled. This means that if you disable a plugin containing a servlet,
or a single servlet module, and re-enable it again, the servlet is re-instantiated and its init() method will be called again.
Because all servlet modules are deployed beneath a common /plugins/servlet root, be careful when choosing each
url-pattern under which your servlet is deployed. It is recommended to use a value that will always be unique to the world!
RELATED TOPICS
Writing Confluence Plugins
Installing a Plugin
Information sourced from Plugin Framework documentation
Spring Component Module - Old Style
Available:
Confluence 2.2 and later
Deprecated:
Confluence 2.10 – use the new Component Module Type instead
Old-Style Plugin Module Type
We recommend that you use the new plugin module type, rather than the old-style Spring Component described below. Confluence still
supports the earlier module type, but the new OSGi-based plugin framework fixes a number of bugs and limitations experienced by the
old-style plugin modules.
Purpose of this Module Type
A Spring module allows you to use standard Spring XML configuration tags.
A Spring module appears in atlassian-plugin.xml like this:
<spring name="Space Cleaner Job" key="spaceCleanerJob"
class="org.springframework.scheduling.quartz.JobDetailBean">
... any standard spring configuration goes here...
</spring>
The above is equivalent to the following configuration in applicationContext.xml:
<bean id="spaceCleanerJob" class="org.springframework.scheduling.quartz.JobDetailBean">
...
</bean>
Ordering of Components
If you declare a Spring component that refers to another Spring component, you must ensure the referred component is declared first. For
example:
<spring name="Bean A" key="beanA"
class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
...
</spring>
<spring name="Bean B" key="beanB" alias="soapServiceDelegator"
class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target">
<ref local="beanA"/>
</property>
...
</spring>
Notice that beanB refers to beanA and that beanA is declared before beanB. If you don't do it in this order, Confluence will complain that
beanA does not exist.
RELATED TOPICS
Component Module
Writing Confluence Plugins
Installing a Plugin
Theme Module
Available:
Confluence 1.3 and later
Themes define a look and feel for Confluence. Confluence ships with several themes that you can use, such as the default theme and or the
left-nav theme. Theme plugins, on the other hand, allow you to create your totally customized look and feel. A theme can be applied to an
entire Confluence site or to individual spaces.
Stylesheet themes
Creating a theme which only relies on stylesheets and images is much simpler than customising HTML, and more likely to work in future
versions of Confluence.
Creating a Stylesheet Theme
Custom HTML themes
Creating a new theme with custom HTML consists of two steps:
1. Creating a theme with decorators and colour schemes, which defines how each page looks.
2. Packaging and Installing a Theme Plugin - themes are part of our plugin system.
Installing your theme
To install it within Confluence, please read Installing a Plugin.
Example themes
There are several other themes that you can use as examples to learn from and extend.
Stylesheet themes:
Easy Blue Theme
Custom HTML themes:
Clickr Theme
Comment Tab Theme
DOC:Left-nav Theme
We've also prodvided a DOC:Confluence Space export for theme developers. You can import this space into your development Confluence,
and check each page to make sure all of the Confluence content looks good in your new theme.
You may also want to read Including Cascading Stylesheets in Themes.
Adding a Theme Icon
You can package a theme icon with a theme to give the user a preview of how the theme will change the layout of Confluence. If you do not
specify a custom icon for your theme, a default icon will be shown in the preview.
Defining the theme icon in the atlassian-plugin.xml
To include an icon in the theme, you will need to reference it as a Downloadable Plugin Resource from within the theme module.
Here is an example where an icon called my-theme-icon.gif is used in the Dinosaur Theme:
<theme key="dinosaurs" name="Dinosaur Theme" class="com.atlassian.confluence.themes.BasicTheme">
<description>A nice theme for the kids</description>
<colour-scheme key="com.example.themes.dinosaur:earth-colours"/>
<layout key="com.example.themes.dinosaur:main"/>
<layout key="com.example.themes.dinosaur:mail-template"/>
<resource name="themeicon.gif" type="download"
location="com/example/themes/dinosaur/my-theme-icon.gif">
<property key="content-type" value="image/gif"/>
</resource>
</theme>
The resource parameter takes three arguments:
Name: The name of the icon (
has to be themeicon.gif).
Type: The type of resource-in this instance, 'download'.
Location: The location of the file represented in the jar archive you will use to bundle your theme.
The icon will automatically appear on the themes screen in the space and global administration and will be displayed next to the text and
description of the theme.
Creating your own theme icon
In order to keep the look and feel of the icons consistent, we recommend that you base the icon style on icons shipped with the Confluence
themes. A good starting point when creating new icons is to use the default theme icon:
Creating a Stylesheet Theme
To create a stylesheet theme, you need to first create your custom
stylesheet for Confluence. You can do this using many CSS editing tools.
See Styling Confluence with CSS for more information.
Once you have a stylesheet (and optionally images) ready, this guide will
show you how to package up your stylesheet for use in Confluence as a
theme.
Quick demonstration
The quick demonstration is the Easy Blue theme, which you can
download here:
easy-blue-theme-1.1.jar
You can quickly customise this theme by using a ZIP extractor program
(like WinZip, 7-Zip, etc.) to extract the files, change them, then zip it back
up into a JAR file and install it in Confluence.
Easy Blue theme screenshot
Since this theme was developed as a quick stylesheet demonstration for Confluence, it only has limited browser support. See Easy Blue
Stylesheet for information about which browsers are supported.
The remainder of this document is a walk-through which describes in detail how to create a theme like this from scratch.
Creating the descriptor file
Each theme plugin needs a plugin descriptor file, called atlassian-plugin.xml. For themes with a single stylesheet, the file is very
simple. Below is an example with one stylesheet.
Theme atlassian-plugin.xml with one stylesheet
<atlassian-plugin name="Simple Demo Theme" key="com.example.acme.simple">
<plugin-info>
<description>A Confluence stylesheet theme.</description>
<vendor name="Acme Software Pty Ltd" url="http://acme.example.com"/>
<version>1.0</version>
</plugin-info>
<theme key="simple-theme" name="Simple Demo Theme"
class="com.atlassian.confluence.themes.BasicTheme">
<!-- CSS -->
<resource type="download" name="demo-theme.css" location="demo-theme.css"/>
<param name="includeClassicStyles" value="false"/>
</theme>
</atlassian-plugin>
To create your new theme from scratch:
Copy the above XML into a new text file
Customise the key, name and vendor of your theme throughout the file
Don't change the class in the <theme> tag
In the <resource> tag, put the name of your stylesheet in both the name and the location attributes
Save the customised XML file as atlassian-plugin.xml in a new directory with your stylesheet.
Packaging the theme
The theme files need to be put into a JAR file. A JAR file is essentially a ZIP file with a .jar extension, so you can create it with whatever
tool you like.
To use the command-line tool, jar, which ships with the Java Development Kit, you can run the following command in the directory with your
files:
jar cf my-awesome-theme-1.0.jar *.xml *.css *.gif *.png
This will wrap up the atlassian-plugin.xml file with whatever images and CSS files you have in your directory. Now you can upload the
plugin into Confluence.
Now you're done! If your theme is working great now, then you're finished. There might be a few more things you need to know, however.
The later sections cover these details about further customisation of your stylesheet theme.
Including the default stylesheet
Most themes that you write for Confluence will actually rely on the default theme stylesheets in Confluence. This includes the standard
Confluence fonts, colours, and many other things. To include the Confluence styles in your theme, the <theme> tag in your plugin needs to
include Confluence's default stylesheet as a resource:
Theme atlassian-plugin.xml which has one stylesheet, and extends Confluence's default theme
<atlassian-plugin>
...
<theme key="simple-theme" name="Simple Demo Theme"
class="com.atlassian.confluence.themes.BasicTheme">
<!-- CSS -->
<resource type="download" name="default-theme.css" location="/includes/css/default-theme.css">
<param name="source" value="webContext"/>
</resource>
<resource type="download" name="demo-theme.css" location="demo-theme.css"/>
<param name="includeClassicStyles" value="false"/>
</theme>
</atlassian-plugin>
Including images
For many themes, you will want to pull in custom background images, icons, and so on. This is very easy to do:
Put the images in the same directory as your CSS and atlassian-plugin.xml files.
Add a resource to your theme descriptor XML file for the image:
<atlassian-plugin>
...
<theme key="simple-theme" name="Simple Demo Theme"
class="com.atlassian.confluence.themes.BasicTheme">
<!-- CSS -->
<resource type="download" name="default-theme.css" location="/includes/css/default-theme.css">
<param name="source" value="webContext"/>
</resource>
<resource type="download" name="image-theme.css" location="image-theme.css"/>
<!-- Images -->
<resource type="download" name="home-16.png" location="home-16.png"/>
<param name="includeClassicStyles" value="false"/>
</theme>
</atlassian-plugin>
Images in subdirectories (optional)
You can put your images into a subdirectory within your theme if you want to:
...
<!-- Images -->
<resource type="download" name="filename.gif" location="images/filename.gif"/>
...
Note that when you reference that file in your CSS, you simply use the name and not the path in the location. For this example:
.selector {
background-image: url("filename.gif");
}
Theme icon image (optional)
To polish off your new theme, you can create a theme icon for the "Choose Theme" menu (displayed next to your theme's name and
description). If you don't set your own icon, a default image will be shown. Your theme will work either way.
To create a theme icon:
make a 110px × 73px .gif which represents your theme
add the image to your theme
set the location attribute (but don't change the name) with the following <resource>:
<theme key="simple-theme" name="Simple Demo Theme"
class="com.atlassian.confluence.themes.BasicTheme">
...
<resource key="icon" name="themeicon.gif" type="download"
location="your-theme-icon.gif"/>
...
</theme>
Sample theme
Here's a listing of the files in the source of the Easy Blue theme (demonstrated above):
atlassian-plugin.xml
divider.png
easy-blue-theme.css
gradient-comment-side-light.png
gradient-comment-side.png
gradient-comments-light.png
gradient-comments.png
gradient-dark-invert.png
gradient-dark.png
gradient-light.png
home-16.png
theme-icon.gif.
These are all zipped up into the easy-blue-theme-1.1.jar file which can be installed into Confluence. In fact, the JAR file is almost exactly the
same as the ZIP file. The only difference is a manifest file generated automatically by the jar command line tool, which is completely
unnecessary for your theme to work in Confluence.
Here's the plugin descriptor file:
<atlassian-plugin name="Easy Blue Theme" key="com.atlassian.confluence.ext.theme.easyblue">
<plugin-info>
<description>A Confluence theme with soft gradients and easy blue colours.</description>
<vendor name="Atlassian Software Systems Pty Ltd" url="http://www.atlassian.com"/>
<version>1.1</version>
</plugin-info>
<theme key="easyblue" name="Easy Blue Theme" class="com.atlassian.confluence.themes.BasicTheme">
<!-- CSS -->
<resource type="download" name="default-theme.css" location="/includes/css/default-theme.css">
<param name="source" value="webContext"/>
</resource>
<resource type="download" name="easy-blue-theme.css" location="easy-blue-theme.css"/>
<!-- Images -->
<resource type="download" name="divider.png" location="divider.png"/>
<resource type="download" name="gradient-comment-side-light.png"
location="gradient-comment-side-light.png"/>
<resource type="download" name="gradient-comment-side.png"
location="gradient-comment-side.png"/>
<resource type="download" name="gradient-comments-light.png"
location="gradient-comments-light.png"/>
<resource type="download" name="gradient-comments.png" location="gradient-comments.png"/>
<resource type="download" name="gradient-dark-invert.png"
location="gradient-dark-invert.png"/>
<resource type="download" name="gradient-dark.png" location="gradient-dark.png"/>
<resource type="download" name="gradient-light.png" location="gradient-light.png"/>
<resource type="download" name="home-16.png" location="home-16.png"/>
<param name="includeClassicStyles" value="false"/>
<resource key="icon" name="themeicon.gif" type="download" location="theme-icon.gif"/>
</theme>
</atlassian-plugin>
You should ensure you update the plugin details if you copy this example.
Creating a Theme
Using Decorators
Using Stylesheets
Using Colour Schemes
Using Decorators
A decorator defines Confluence page layout. By modifying a decorator file, you can move "Attachments' tab from the left of the screen to the
right or remove it completely. Decorator files are written in the Velocity templating language and have the VMD extension. You can familiarise
yourself with Velocity at the Velocity Template Overview and decorators in general at the Sitemesh homepage.
Decorators, Contexts and Modes
Confluence comes bundled with a set of decorator files that you can customize. Instead of having one decorator file for each screen, we've
grouped together similar screens (example: view and edit page screens) to simplfy editing layouts.
There is some terminology that we use when talking about decorators that should be defined. We've grouped all the screens in Confluence
into major categories which we call contexts. Within each context are various modes (ways of viewing that particular layout).
The following table summarises how decorators use contexts and modes:
Decorator
Context
main.vmd
n/a (header and
footer formatting)
page.vmd
page
Mode
Comment
main.vmd is used to control the header and footer of each
page, not the page specific presentation logic
'view', 'edit', 'edit-preview',
'view-information', and
'view-attachments'
blogpost.vmd
blogpost (news)
'view', 'edit', 'edit-preview', and
'remove'
mail.vmd
mail
'view', 'view-thread' and 'remove'
global.vmd
global
'dashboard', 'view-profile',
'edit-profile',
'change-password-profile',
'edit-notifications-profile'
space.vmd
space-pages
list-alphabetically,
list-recently-updated,
list-content-tree, create-page
space-mails
view-mail-archive
space-blogposts
view-blogposts, create-blogpost
space-templates
view-templates
space-operations
view-space-operations"
space-administration
view-space-administration,
list-permission-pages
We prefer to use 'news' as an end-user term; all templates
and classes use 'blogpost' to indicate RSS related content
space.vmd handles a wide range of options, this context is
accessed by clicking on 'browse space' in the default theme
of Confluence (tabbed theme)
Example
As an example on how to use the table above, say we found the 'Attachments' tab on the view page screen annoying and wanted to remove
it. We could make this layout change in the page.vmd file - where the 'view' mode is handled (as shown below).
#*
Display page based on mode: currently 'view', 'edit', 'preview-edit', 'info' and
'attachments.
See the individual page templates (viewpage.vm, editpage.vm, etc.) for the setting of the
mode parameter.
*#
## VIEW
#if ($mode == "view")
<make layout modifications here>
#elseif ...
When creating your own decorators, it is critical that you preserve the lines #parse
("/pages/page-breadcrumbs.vm") or #parse ("/breadcrumbs.vm"). These include files pass important
information about the space to other space decorators and hence must be included.
The Theme Helper Object
When editing decorator files you will come across a variable called $helper - this is the theme helper object.
The following table summarises what this object can do:
Behaviour
Explanation
$helper.domainName
displays the base URL of your Confluence instance on your page. This is
useful for constructing links to your own Confluence pages.
$helper.spaceKey()
returns the current space key or null if in a global context.
$helper.spaceName
returns the name of the current space
$helper.renderConfluenceMacro("{create-space-button}")
renders a call to a [Confluence Macro] for the velocity context
$helper.getText("key.key1")
looks up a key in a properties file matching key.key1=A piece of text
and returns the matching value ("A piece of text")
$helper.action
returns the XWork action which processed the request for the current page.
If you are on a page or space screen you also have access to the actual page and space object by using $helper.page and
$helper.space respectively.
If you want to delve more into what other methods are available in this object, please see our API's for ThemeHelper.
Velocity macros
Finally, the last thing you need to decipher decorator files is an understanding of macros. A velocity macro looks like this:
#myVelocityMacro()
In essence, each macro embodies a block of code. We've used these macros to simplify decorator files and make them easier to modify.
For example, the #editPageLink() macro will render the edit page link you see on the 'View Page Screen'. All the logic which checks
whether a certain user has permissions to edit pages and hence see the link are hidden in this macro. As the theme writer, you need only
care about calling it.
The easiest way to acquaint yourself with the macros is to browse through your macros.vm file, located in
/template/includes/macros.vm (under the base Confluence installation).
Writing your own Velocity Macros
Velocity macros are very useful for abstracting out common presentation logic into a function call and for keeping decorators clean. If you
wish to use them for your theme you can either:
Write your own Macros file
Write your own Velocity macros library file, as we've done with macros.vm. If you elect to do this you must locate the velocity.properties file
beneath WEB-INF/classes and tell the Velocity engine where your library file can be located, relative to the base installation of Confluence.
velocimacro.library = template/includes/macros.vm
Use Inline Velocity Macros.
Inline velocity macros, when loaded once, can be called from anywhere. See decorators/mail.vmd for examples of inline decorators.
Using Stylesheets
Stylesheets can be defined for a theme and they will automatically be included by Confluence when pages are displayed with your theme.
You simply need to add a resource of type download to your theme module. Please note that the resource name must end with .css for it
to be automatically included by Confluence.
<theme key="mytheme" .... >
...
<resource type="download" name="my-theme.css" location="styles/my-theme.css"/>
...
</theme>
Now, in the HTML header of any page using your theme, a link tag to your theme stylesheets will be created by Confluence. If you have a
look at the source of combined.css, it will contain imports to all your theme stylesheets.
<html>
<head>
...
<link type="text/css" href="/confluence/s/.../_/styles/combined.css?spaceKey=FOO"
rel="stylesheet">
</head>
...
</html>
Theme stylesheets are included after all the default Confluence styles and colour schemes. This is to ensure that your theme styles can
override and take precedence over the base styles provided by Confluence.
Using Colour Schemes
Users can customise their own colour scheme (regardless of the theme selected) for a particular space under Space Administration.
You may choose to respect these user configured colour schemes in your theme or ignore them completely by overriding them in your theme
stylesheets. If you would like to respect the configured colour schemes for your new UI elements, you should specify a velocity stylesheet
resource in your theme module.
<theme key="mytheme" .... >
...
<resource type="stylesheet" name="my-theme-colors.vm"
location="templates/clickr/my-theme-colors.vm"/>
...
</theme>
Please note that the resource name must end with .vm, and the type must be 'stylesheet' for it to be automatically rendered as a velocity
template by Confluence. This velocity stylesheet will essentially contain css for colours with references to the colour scheme bean (which is
available to you via the action). For example:
\#breadcrumbs a {
color: $action.colorScheme.linkColor;
}
#myNewElement {
color: $action.colorScheme.headingTextColor;
}
.myNewElementClass {
border-color: $action.colorScheme.borderColor;
}
...
As the velocity stylesheet is rendered as a velocity template, you will need to escape any #ids (e.g. breadcrumbs) that
match velocity macro names.
Additionally, you may choose to provide your theme with a pre-defined colour scheme (which users will be able to select under Space
Administration). This pre-defined colour scheme will take precedence if no custom user one is defined for the space. To define a theme's
colour scheme, you need to add a colour scheme module and link to it in the theme module. For example:
<theme key="mytheme" .... >
...
<colour-scheme key="com.atlassian.confluence.themes.mytheme:earth-colours"/>
...
</theme>
...
<colour-scheme key="earth-colours" name="Brown and Red Earth Colours"
class="com.atlassian.confluence.themes.BaseColourScheme">
<colour key="property.style.topbarcolour" value="#440000"/>
<colour key="property.style.spacenamecolour" value="#999999"/>
<colour key="property.style.headingtextcolour" value="#663300"/>
<colour key="property.style.linkcolour" value="#663300"/>
<colour key="property.style.bordercolour" value="#440000"/>
<colour key="property.style.navbgcolour" value="#663300"/>
<colour key="property.style.navtextcolour" value="#ffffff"/>
<colour key="property.style.navselectedbgcolour" value="#440000"/>
<colour key="property.style.navselectedtextcolour" value="#ffffff"/>
</colour-scheme>
The class of a colour scheme must implement com.atlassian.confluence.themes.ColourScheme. The
com.atlassian.confluence.themes.BaseColourScheme class provided with Confluence sets the colours based on the module's
configuration.
The available colours correspond to those that you would configure under Space Administration > Colour Scheme:
Key
Description
Default value
property.style.topbarcolour
The strip across the top of the page
#003366
property.style.breadcrumbstextcolour
The breadcrumbs text in the top bar of the page
#ffffff
property.style.spacenamecolour
The text of the current space name, or Confluence in the top left
#999999
property.style.headingtextcolour
All heading tags throughout the site
#003366
property.style.linkcolour
All links throughout the site
#003366
property.style.bordercolour
Table borders and dividing lines
#3c78b5
property.style.navbgcolour
Background of tab navigation buttons
#3c78b5
property.style.navtextcolour
Text of tab navigational buttons
#ffffff
property.style.navselectedbgcolour
Background of tab navigation buttons when selected or hovered
#003366
property.style.navselectedtextcolour
Text of tab navigation buttons when selected or hovered
#ffffff
property.style.topbarmenuselectedbgcolour
Background of top bar menu when selected or hovered
#336699
property.style.topbarmenuitemtextcolour
Text of menu items in the top bar menu
#003366
property.style.menuselectedbgcolour
Background of page menu when selected or hovered
#6699cc
property.style.menuitemtextcolour
Text of menu items in the page menu
#535353
property.style.menuitemselectedbgcolour
Background of menu items when selected or hovered
#6699cc
property.style.menuitemselectedtextcolour
Text of menu items when selected or hovered
#ffffff
Packaging and Installing a Theme Plugin
The Theme Plugin Module
The theme module defines the theme itself. When someone in Confluence selects a theme either globally or within a space, they are
selecting from the available theme modules.
<theme key="dinosaurs" name="Dinosaur Theme"
class="com.atlassian.confluence.themes.BasicTheme">
<description>A nice theme for the kids</description>
<colour-scheme key="com.example.themes.dinosaur:earth-colours"/>
<layout key="com.example.themes.dinosaur:main"/>
<layout key="com.example.themes.corporate:mail-template"/>
</theme>
The class of a theme must implement com.atlassian.confluence.themes.Theme. The
com.atlassian.confluence.themes.BasicTheme class provided with Confluence gathers together all the resources listed within the
module definition into a theme.
A theme can contain an optional colour-scheme element that defines which colour-scheme module this theme will use, and any number of
layout elements that define which layouts should be applied in this theme. Refer to these modules by the complete module key.
It is possible for a theme to use modules that aren't in the same plugin as the theme. Just keep in mind that your theme will be messed up if
some plugin that the theme depends on is removed.
Installing the Theme
Themes are installed as 'plugin modules'. The plugin module is a collection of files, usually zipped up in a JAR archive, which tells
Confluence how to find the decorators and colour-scheme of your theme.
You can use plugins in Confluence for many purposes, one of which is themes. In every case, the central configuration file, which describes
the plugin to Confluence, is named atlassian-plugin.xml.
There are two steps to creating the plugin module.
1. Create the central configuration file for the theme: atlassian-plugin.xml
2. Create the JAR archive for your theme: bundling your theme
Writing the atlassian-plugin.xml file for your theme
The structure of an atlassian-plugin.xml file is fairly self-explanatory. In the code segment below you will find a full example of an
atlassian-plugin.xml file, showing:
each of the decorators you have defined to customize Confluence
your colour scheme
in a way which Confluence can use to override the default theme. In other words, this XML tells Confluence to look in certain locations for
replacement decorators when processing a request.
<atlassian-plugin key="com.atlassian.confluence.themes.tabless" name="Plain Theme">
<plugin-info>
<description>
This theme demonstrates a plain look and feel for Confluence.
It is useful as a building block for your own themes.
</description>
<version>1.0</version>
<vendor name="Atlassian Software Systems Pty Ltd" url="http://www.atlassian.com/"/>
</plugin-info>
<theme key="tabless" name="Tabless Theme" class="com.atlassian.confluence.themes.BasicTheme">
<description>plain Confluence theme.</description>
<layout key="com.atlassian.confluence.themes.tabless:main"/>
<layout key="com.atlassian.confluence.themes.tabless:global"/>
<layout key="com.atlassian.confluence.themes.tabless:space"/>
<layout key="com.atlassian.confluence.themes.tabless:page"/>
<layout key="com.atlassian.confluence.themes.tabless:blogpost"/>
<layout key="com.atlassian.confluence.themes.tabless:mail"/>
<colour-scheme key="com.atlassian.confluence.themes.tabless:earth-colours"/>
// Optional: for themes which need configuration.
<param name="space-config-path" value="/themes/tabless/configuretheme.action"/>
<param name="global-config-path" value="/admin/themes/tabless/configuretheme.action"/>
</theme>
<layout key="main" name="Main Decorator"
class="com.atlassian.confluence.themes.VelocityDecorator"
overrides="/decorators/main.vmd">
<resource type="velocity" name="decorator"
location="com/atlassian/confluence/themes/tabless/main.vmd"/>
</layout>
<layout key="global" name="Global Decorator"
class="com.atlassian.confluence.themes.VelocityDecorator"
overrides="/decorators/global.vmd">
<resource type="velocity" name="decorator"
location="com/atlassian/confluence/themes/tabless/global.vmd"/>
</layout>
<layout key="space" name="Space Decorator"
class="com.atlassian.confluence.themes.VelocityDecorator"
overrides="/decorators/space.vmd">
<resource type="velocity" name="decorator"
location="com/atlassian/confluence/themes/tabless/space.vmd"/>
</layout>
<layout key="page" name="Page Decorator"
class="com.atlassian.confluence.themes.VelocityDecorator"
overrides="/decorators/page.vmd">
<resource type="velocity" name="decorator"
location="com/atlassian/confluence/themes/tabless/page.vmd"/>
</layout>
<layout key="blogpost" name="Blogpost Decorator"
class="com.atlassian.confluence.themes.VelocityDecorator"
overrides="/decorators/blogpost.vmd">
<resource type="velocity" name="decorator"
location="com/atlassian/confluence/themes/tabless/blogpost.vmd"/>
</layout>
<layout key="mail" name="Mail Decorator"
class="com.atlassian.confluence.themes.VelocityDecorator"
overrides="/decorators/mail.vmd">
<resource type="velocity" name="decorator"
location="com/atlassian/confluence/themes/tabless/mail.vmd"/>
</layout>
<colour-scheme key="earth-colours" name="Brown and Red Earth Colours"
class="com.atlassian.confluence.themes.BaseColourScheme">
<colour key="topbar" value="#440000"/>
<colour key="spacename" value="#999999"/>
<colour key="headingtext" value="#663300"/>
<colour key="link" value="#663300"/>
<colour key="border" value="#440000"/>
<colour key="navbg" value="#663300"/>
<colour key="navtext" value="#ffffff"/>
<colour key="navselectedbg" value="#440000"/>
<colour key="navselectedtext" value="#ffffff"/>
</colour-scheme>
</atlassian-plugin>
The class which each decorator, or layout, is mapped to must implement com.atlassian.confluence.themes.VelocityDecorator.
The layout entry must provide an overrides attribute which defines which decorator within Confluence is being overridden by the theme.
Importantly, when telling Confluence to override a particular decorator with another one, the location of the custom decorator is specified. For
example:
<layout key="page" name="Page Decorator" class="com.atlassian.confluence.themes.VelocityDecorator"
overrides="/decorators/page.vmd">
<resource type="velocity" name="decorator"
location="com/atlassian/confluence/themes/tabless/page.vmd"/>
</layout>
The location attribute needs to be represented in the JAR archive you will use to bundle your theme.
Bundling the Theme
Your decorators should be placed in a directory hierarchy which makes sense to you. The atlassian-plugin.xml file should be placed at
the top level of the directory structure, afterwards the decorators should be placed in directories which make a meaningful division of what
they do. It is your choice as to how the structure is laid out, each decorator could even be placed alongside atlassian-plugin.xml}}. The
essential thing is for the location attribute of each decorator to accurately tell Confluence how to load it.
Thus, a recursive directory listing of the example theme above gives:
atlassian-plugin.xml
com/atlassian/confluence/themes/tabless/
com/atlassian/confluence/themes/tabless/global.vmd
com/atlassian/confluence/themes/tabless/space.vmd
com/atlassian/confluence/themes/tabless/mail.vmd
com/atlassian/confluence/themes/tabless/blogpost.vmd
com/atlassian/confluence/themes/tabless/main.vmd
com/atlassian/confluence/themes/tabless/page.vmd
Theme Configuration
The themes can be configured via the Configuration link on the Choose Theme page on both the space and global level.
For example, the Left Navigation Theme allows for the specification of the title of the page which page should be used for navigation. The
XWork module allows for developing complex configurations for themes, which can be saved in a config file.
Setting up the atlassian-plugin.xml
Configuration path parameter.
Specify the path to the configuration action in the atlassian-plugin.xml:
<theme key="dinosaurs" name="Dinosaur Theme"
class="com.atlassian.confluence.themes.BasicTheme">
<description>A nice theme for the kids</description>
<colour-scheme key="com.example.themes.dinosaur:earth-colours"/>
<layout key="com.example.themes.dinosaur:main"/>
<layout key="com.example.themes.corporate:mail-template"/>
<param name="space-config-path" value="/themes/dinosaurs/configuretheme.action"/>
<param name="global-config-path" value="/admin/themes/dinosaurs/configuretheme.action"/>
</theme>
Note that two new parameters have been specified in the above xml.
space-config-path points to the action connected with the space theme configuration.
global-config-path points to the action connected with the global theme configuration.
As themes can be specified either on a global or space level, different configuration actions can be implemented for each level. If there is no
need for configuration on a level, simply don't specify the config path.
XWork Actions
Specify the configuration actions via the Xwork plugin module.
Define two packages, one for the space-level and one for the global configuration.
<xwork name="themeaction" key="themeaction">
<package name="dinosaurs" extends="default" namespace="/themes/dinosaurs">
<default-interceptor-ref name="defaultStack" />
<action name="configuretheme"
class="com.atlassian.confluence.extra.dinosaurs.ConfigureThemeAction" method="doDefault">
<result name="input" type="velocity">/templates/dinosaurs/configuretheme.vm</result>
</action>
<action name="doconfiguretheme"
class="com.atlassian.confluence.extra.dinosaurs.ConfigureThemeAction">
<result name="success" type="redirect">/spaces/choosetheme.action?key=${key}</result>
</action>
</package>
<package name="dinosaurs-admin" extends="default" namespace="/admin/themes/dinosaurs">
<default-interceptor-ref name="defaultStack" />
<action name="configuretheme"
class="com.atlassian.confluence.extra.dinosaurs.ConfigureThemeAction" method="doDefault">
<result name="input"
type="velocity">/templates/dinosaurs/configurethemeadmin.vm</result>
</action>
<action name="doconfiguretheme"
class="com.atlassian.confluence.extra.dinosaurs.ConfigureThemeAction">
<result name="success" type="redirect">/admin/choosetheme.action</result>
</action>
</package>
</xwork>
configuretheme defines the velocity file used to display the input view.
doconfiguretheme defines the action to redirect to after the configuration was successful.
Note that the config-path parameters specified above matches the namespace plus the name of the action.
For example, given the above atlassian-plugin.xml, the configuretheme action would be accessed at
http://yourserver/confluence/themes/dinosaurs/configuretheme.action
.
The namespace of the global action has to start with /admin. Otherwise the action will not be decorated by the admin
decorator, and the navigation of the admin area will not be visible.
Saving Theme Configurations with Bandana
To persist the configuration of a theme you can make use of the Bandana persistence framework.
For example, the Left Navigation Theme uses the persister to store it's configuration values.
Defining a Settings Bean
The recommended way of saving the settings, is to create a simple configuration bean which implements the Serializable interface.
The bean for the Left Navigation Theme for example, simply consists of two String variables and their getter and setter methods.
package com.atlassian.confluence.extra.leftnavigation;
import java.io.Serializable;
public class LeftNavSettings implements Serializable
{
private String space;
private String page;
public String getSpace()
{
return space;
}
public void setSpace(String space)
{
this.space = space;
}
public String getPage()
{
return page;
}
public void setPage(String page)
{
this.page = page;
}
}
Saving the Bean
Bandana can be used to save a configuration object with a given context, where the context refers to a space.
The setValue function of the BandanaManager has three arguments:
@param context The context to store this value in
@param key The key of the object
@param value The value to be stored
// Create a setting bean.
LeftNavSettings settings = new LeftNavSettings();
settings.setSpace("example Space");
settings.setPage("example Page");
// Save the bean with the BandanaManager
bandanaManager.setValue(new ConfluenceBandanaContext(spaceKey), THEMEKEY, settings);
A Context can be defined on two levels:
Global: new ConfluenceBandanaContext()
Space level: new ConfluenceBandanaContext(spaceKey)
Retrieving the Bean
The configuration object can be retrieved by using bandanaManager.getValue. This method will get the configuration object, starting with the
given context and looking up in the context hierarchy if no context is found.
@param context The context to start looking in
@param key The key of the BandanaConfigurationObject object
@return Object object for this key, or null if none exists.
LeftNavSettings settings = (LeftNavSettings) bandanaManager.getValue(new
ConfluenceBandanaContext(spaceKey), THEMEKEY);
Updating a theme for editable comments
This is a simple how-to that shows the steps to upgrade your plugin for editable comments.
Modify sharedcomments.vmd
Making your themes compatible with editable comment only requires modifying sharedcomments.vmd. There are 3 parts to update. A good
example of this is the Clickr Theme.
Adding the edit link
First to enable editable comment you will need to give access to the edit function.
Adding the link is as simple as adding the following piece of code near your existing 'Permalink' and 'Remove Comment' links:
#if ($permissionHelper.canEdit($remoteUser, $comment ))
| <a id="edit-$comment.id"
href="$req.contextPath$generalUtil.customGetPageUrl($page)showComments=true&amp;editComment=true&amp;focusedComment
Enable inline editing
Editing a comment happens inline. Therefore the editor must be added when rendering the comment being edited as follow:
#if ($focusedCommentId == $comment.id && $action.editComment &&
$permissionHelper.canEdit($remoteUser, $comment))
<form name="editcommentform" method="POST"
action="$req.contextPath/pages/doeditcomment.action?pageId=$page.id&amp;commentId=$comment.id">
#bodytag (Component "name='content'" "theme='notable'" "template='wiki-textarea.vm'")
#param ("formname" "editcommentform")
#param ("spaceKey" "$generalUtil.urlEncode($spaceKey)")
#param ("rows" 15)
#param ("cols" 70)
#param ("width" "100%")
#param ("tabindex" "4")
#param ("tdcolor" "f0f0f0")
#param ("toolbarExpanded" "false")
#param ("initialFocus" "false")
#param ("edit" "true")
#param ("heartbeat" "false")
#param ("wikiContent" "$comment.content")
#param ("wysiwygContent"
"$action.helper.wikiStyleRenderer.convertWikiToXHtml($comment.toPageContext(), $comment.content)")
#end
#commentSubmission()
</form>
#else
## your current comment rendering...
#end
Add update information
This step is optional but it always nice for user to knwo when a comment has been updated and by who. The following piece of code gets the
necessary information.
#if ( $action.helper.shouldRenderCommentAsUpdated($comment) )
#if ( $comment.creatorName == $comment.lastModifierName )
$action.getText("comment.updated.by.author", ["#usernameLink ($comment.lastModifierName)",
$action.dateFormatter.formatDateTime( $comment.lastModificationDate )])
#else
$action.getText("comment.updated.by.non.author", ["#usernameLink ($comment.lastModifierName)",
$action.dateFormatter.formatDateTime( $comment.lastModificationDate )])
#end
#end
The shouldRenderCommentAsUpdated method is a convenience method that checks whether the comment has been updated by its
creator more than 10 minutes after being created. It exists so that comments will not get cluttered with useless information because of a
quick fix made shortly after the comment is posted. One can adjust the time frame by passing a number of seconds as the second argument
to this method.
Finally, if the updater of the comment is different to the original author of the comment, his name is displayed.
Trigger Module
Available:
Confluence 2.2 and later
Changed:
In Confluence 3.5 and later, the trigger element accepts an optional child element (managed), which defines
the scheduled job's availability in Confluence's administration console.
Trigger plugin modules enable you to schedule when your Job Module are scheduled to run Confluence.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
Trigger Plugin Module
The Trigger plugin module schedules Jobs within a plugin. Triggers are one of two types:
cron - jobs are scheduled using cron syntax
simple - jobs are scheduled to repeat every X seconds
Here is an example atlassian-plugin.xml fragment containing a Job with it's corresponding Trigger module using a cron-style
expression (for reference, this expression will execute the job with key 'myJob' every minute):
<atlassian-plugin name="Sample Component" key="confluence.extra.component">
...
<job key="myJob"
name="My Job"
class="com.example.myplugin.jobs.MyJob" />
<trigger key="myTrigger" name="My Trigger">
<job key="myJob" />
<schedule cron-expression="0 * * * * ?" />
<managed editable="true" keepingHistory="true" canRunAdhoc="true" canDisable="true" />
</trigger>
...
</atlassian-plugin>
The trigger element accepts the following attributes:
name — represents how this component will be referred to in the Confluence interface.
key — represents the internal, system name for your Trigger.
class — represents the class of the Job to be created. This class must have a constructor that takes no arguments. Otherwise, the
class cannot be instantiated by Confluence.
The trigger element also accepts the following elements:
schedule — defines a cron expression in its cron-expression attribute. For more information on cron expressions can be found
on the Scheduled Jobs page (or the Cron Trigger tutorial on the Quartz website).
managed (Available in Confluence 3.5 and later only) — an optional element that defines the configuration of the job on the
Scheduled Jobs administration page. If the managed element is omitted, then the job will not appear in the Scheduled Jobs
administration page. This element takes the following attributes each of which take the values of either true or false:
If any of these attributes are omitted, their values are assumed to be false.
editable — If true, the job's schedule can be edited.
keepingHistory — If true, the job's history persists and survives server restarts. If false, the job's history is only
stored in memory and will be lost upon the next server restart.
canRunAdhoc — If true, the job can be executed manually on the Scheduled Jobs administration screen.
canDisable — If true, the job can be enabled/disabled on the Scheduled Jobs administration screen.
Here is another example, this time using a simple trigger that repeats every 3600000 milliseconds (1 hour) and will only repeat 5 times:
...
<trigger key="myTrigger" name="My Trigger">
<job key="myJob" />
<schedule repeat-interval="3600000" repeat-count="5" />
</trigger>
...
User Macro Module
Available:
Confluence 2.3 and later
You can create user macros without writing a plugin, by defining the user macro in the Confluence Administration Console.
See Writing User Macros.
Adding a user macro plugin
User Macros are a kind of Confluence plugin module.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
User macro plugin modules are available in Confluence 2.3 or later
In order to upload your plugin via the Universal Plugin Manager (the default plugin manager in Confluence 3.4 and later)
you will need to set the atlassian-plugin attribute pluginsVersion='2' as shown in the example bellow.
User Macro Plugin Modules
User macro plugin modules allow plugin developers to define simple macros directly in the atlassian-plugin.xml file, without writing any
additional Java code. User macro plugin modules are functionally identical to Writing User Macros configured through the administrative
console, except that they can be packaged and distributed in the same way as normal plugins.
User macros installed by plugin modules do not appear in the user macro section of the administrative console, and are
not editable from within the user interface. They appear just as normal plugin modules in the plugin interface.
Configuring a Macro Plugin Module
Macro plugin modules are configured entirely inside the atlassian-plugin.xml file, as follows:
<atlassian-plugin name='Hello World Macro' key='confluence.extra.helloworld' pluginsVersion='2'>
<plugin-info>
<description>Example user macro</description>
<vendor name="Atlassian Software Systems" url="http://www.atlassian.com"/>
<version>1.0</version>
</plugin-info>
<user-macro name='helloworld' key='helloworld' hasBody='true' bodyType='raw'
outputType='html'>
<description>Hello, user macro</description>
<template><![CDATA[Hello, $body!]]></template>
</user-macro>
<!-- more macros... -->
</atlassian-plugin>
The <template> section is required, and defines the velocity template that will be used to render the macro
All the velocity variables available in Writing User Macros are available in user macro plugin modules
The name and key of the macro must be specified the same as Macro Module
No class attribute is required
The attributes of the <user-macro> element match the corresponding configuration for user macros:
Available Attributes
Attribute
Required
Default
Value
Allowed Values
hasBody
No
false
true – the macro expects a body (i.e. {hello}World{hello})
false – the macro does not expect a body (i.e. Hello, {name})
bodyType
No
raw
raw – the body will not be processed before being given to the template
escapehtml – HTML tags will be escaped before being given to the template
rendered – the body will be rendered as wiki text before being given to the template
outputType
No
html
html – the template produces HTML that should be inserted directly into the page
wiki – the template produces Wiki text that should be rendered to HTML before being
inserted into the page
Velocity Context Module
Available:
Confluence 1.4 and later
Velocity Context plugin modules enable you to add components to Confluence's velocity context, making those components available in
templates rendered from decorators, themes, XWork actions or macros.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins
Velocity Context Plugin Module
Each component module adds a single object to Confluence's default velocity context. This context is the collection of objects that are
passed to each velocity template during rendering of macros, decorators, themes and XWork actions. This allows you to create helper
objects that perform tasks too complex to represent in Velocity templates.
The objects are autowired by Spring before being added to the context.
Here is an example atlassian-plugin.xml file containing a single velocity context module:
<atlassian-plugin name="Sample Component" key="confluence.extra.component">
...
<velocity-context-item key="myVelocityHelper"
name="My Plugin's Velocity Helper" context-key="myVelocityHelper"
class="com.example.myplugin.helpers.MyVelocityHelper" />
...
</atlassian-plugin>
the name attribute represents how this component will be referred to in the Confluence interface.
the key attribute represents the internal, system name for your component.
the context-key attribute represents the variable that will be created in Velocity for this item. So if you set a context-key of
myVelocityHelper, the object will be available as $myVelocityHelper in Velocity templates
the class attribute represents the class of the component to be created.
Note: Every velocity context module needs a unique key, or Confluence will not be able to render the module.
WebDAV Resource Module
Available:
Confluence 3.4 and later
WebDAV Resource modules allow you to define new kinds of content within Confluence that can be accessed remotely via the Confluence
WebDAV plugin. You could use this functionality to expose your own custom entities over WebDAV, or expose existing Confluence content
that is not currently accessible over WebDAV.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins.
Configuration
The definition for the WebDAV Resource module resides within the Confluence WebDAV plugin. In order to use the module, you will need to
add a dependency on the WebDAV plugin to your plugin's pom.xml.
The new dependency should look like this:
<dependency>
<groupId>com.atlassian.confluence.extra.webdav</groupId>
<artifactId>webdav-plugin</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
Your plugin will need to be a plugin framework version 2 plugin for the dependency to work. See Version 1 or Version 2 Plugin (Glossary
Entry) and Converting a Plugin to Plugin Framework 2.
You will also need to add a new element to your atlassian-plugin.xml that declares your WebDAV Resource module. The root element
for the module is davResourceFactory. It allows the following attributes and child elements for configuration:
Attributes
Name
Required
Description
class
The class which implements this plugin module. For the WebDAV Resource module, this class
must implement org.apache.jackrabbit.webdav.DavResourceFactory.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
Default
false
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred.
name
The human-readable name of the plugin module.
workspace
The name of the root WebDAV context where your content will be hosted. This workspace name
forms part of the URL to your WebDAV content. For example, a workspace name of 'mywebdav'
would result in a WebDAV URL of /plugins/servlet/confluence/mywebdav. The
workspace name must be unique on your Confluence server. You cannot have multiple WebDAV
Resource modules enabled with the same workspace name.
The
plugin
key.
Elements
Name
description
Required
Description
The description of the plugin module. You can specify the 'key' attribute to declare a localisation key for
the value instead of text in the element body.
Example
Here is an example atlassian-plugin.xml containing a definition for a single WebDAV Resource module:
<atlassian-plugin name="My WebDAV Plugin" key="example.plugin.webdav" plugins-version="2">
<plugin-info>
<description>A basic WebDAV Resource module test</description>
<vendor name="Atlassian Software Systems" url="http://www.atlassian.com"/>
<version>1.0</version>
</plugin-info>
<davResourceFactory key="myResourceFactory" name="My WebDAV Resource Factory"
workspace="mywebdav"
class="example.plugin.webdav.MyResourceFactory">
<description>
Exposes my plugin's content through the Confluence WebDAV plugin.
</description>
</davResourceFactory>
<atlassian-plugin>
Default
The MyResourceFactory class needs to implement the org.apache.jackrabbit.webdav.DavResourceFactory interface. The
purpose of this class is to construct new instances of objects that implement the org.apache.jackrabbit.webdav.DavResource
interface. The factory will typically inspect the incoming request URL from the client in order to determine which DavResource should be
created and returned. For example, a request to /plugins/servlet/confluence/default/Global/DS will return a
com.atlassian.confluence.extra.webdav.resource.SpaceResource for the Confluence Demonstration space.
Here's an example of an implementation for MyResourceFactory:
package example.plugin.webdav;
import com.atlassian.confluence.extra.webdav.ConfluenceDavSession;
import org.apache.jackrabbit.webdav.*;
public class MyResourceFactory implements DavResourceFactory
{
private SettingsManager settingsManager; // used by the HelloWorldResource.
public MyResourceFactory(SettingsManager settingsManager)
{
this.settingsManager = settingsManager;
}
public DavResource createResource(DavResourceLocator davResourceLocator,
DavServletRequest davServletRequest,
DavServletResponse davServletResponse) throws DavException
{
// Delegate this call to the alternative createResource overload
DavSession davSession = (DavSession)
davServletRequest.getSession().getAttribute(ConfluenceDavSession.class.getName());
return createResource(davResourceLocator, davSession);
}
/**
* Returns a reference to the WebDAV resource identified by the current location (represented
in the
* davResourceLocator parameter). For example, a WebDAV request for the URL
* "http://confluence-server/plugins/servlet/confluence/default/ds" would return a {@link
SpaceResource}
* representing the Demonstration Space.
* @param davResourceLocator identifies the requested resource
* @param davSession the current web session
* @return A instance of {@link DavResource} representing the WebDAV resource at the requested
location
* @throws DavException thrown if any kind of non-OK HTTP Status should be returned.
*/
public DavResource createResource(DavResourceLocator davResourceLocator, DavSession
davSession) throws DavException
{
// this is a trivial example that always returns the HelloWorldResource. A more complete
implementation would
// probably examine the davResourceLocator.getResourcePath() value to examine the incoming
request URL.
ConfluenceDavSession confluenceDavSession = (ConfluenceDavSession)davSession;
return new HelloWorldResource(davResourceLocator, this,
confluenceDavSession.getLockManager(), confluenceDavSession, settingsManager);
}
}
As part of your implementation of a WebDAV Resource Module, you will need to create classes that implement DavResource representing
the content you want to expose over WebDAV. The Confluence WebDAV plugin provides a number of base classes that you can inherit from
in order to simplify this step. Here is a brief listing of the classes that your WebDAV resources can inherit from:
AbstractCollectionResource – Inherit from this class when the resource represents a collection of child resources, such as a
Confluence space.
AbstractContentResource – Inherit from this class when the resource is a single entity with no children.
AbstractTextContentResource – Inherit from this class when the resource is a single entity with no children, and the content of the
entity is plain text.
AbstractAttachmentResource – Inherit from this class when the resource is a Confluence page attachment.
AbstractConfluenceResource – Inherit from this class when you require more control over the behaviour of your entity. For example,
if you want to customise the locking behaviour of the resource.
Here is a sample implementation of the HelloWorldResource, which inherits from AbstractTextContentResource.
package example.plugin.webdav;
import org.apache.jackrabbit.webdav.*;
import com.atlassian.confluence.extra.webdav.*;
public class HelloWorldResource extends AbstractTextContentResource
{
private const String HELLO_WORLD = "Hello, World!";
public AbstractTextContentResource(
DavResourceLocator davResourceLocator,
DavResourceFactory davResourceFactory,
LockManager lockManager,
ConfluenceDavSession davSession,
SettingsManager settingsManager)
{
super(davResourceLocator, davResourceFactory, lockManager, davSession, settingsManager);
}
protected long getCreationtTime()
{
return 0;
}
protected byte[] getTextContentAsBytes(String encoding) throws UnsupportedEncodingException
{
return HELLO_WORLD.getBytes(encoding);
}
}
Notes
Your WebDAV Resource module must perform any and all required permission checking when returning WebDAV resources to the
user. See the Confluence Permissions Architecture guide for information on how to perform permission checks.
RELATED TOPICS
Writing Confluence Plugins
Installing a Plugin
Confluence WebDAV Plugin
Web Resource Module
Available:
Atlassian Plugin Framework 1.x and later.
Changed:
In Confluence 2.10 we added the ability to specify web resources like CSS and JavaScript to be included in
specific contexts of Confluence. Please see below for the currently available contexts and more information.
Please take a look at our overview of how and why you should include Javascript and CSS resources into your plugin. The page below gives
specific details of the Web Resource plugin module type.
On this page:
Purpose of this Module Type
Configuration
Attributes
Elements
Example
Referring to Web Resources
Web Resource Contexts
Batched Mode
Non-Batched Mode
Transforming Web Resources
Notes
Web Resource Contexts in Confluence
Purpose of this Module Type
Web Resource plugin modules allow plugins to define downloadable resources. If your plugin requires the application to serve additional
static Javascript or CSS files, you will need to use downloadable web resources to make them available. Web resources are added at the top
of the page in the header with the cache-related headers set to never expire. In addition, you can specify web resources like CSS and
JavaScript to be included in specific contexts within the application.
Configuration
The root element for the Web Resource plugin module is web-resource. It allows the following attributes and child elements for
configuration:
Attributes
Name
Required
Description
class
The class which implements this plugin module. The class you need to provide depends on the
module type. For example, Confluence theme, layout and colour-scheme modules can use classes
already provided in Confluence. So you can write a theme-plugin without any Java code. But for
macro and listener modules you need to write your own implementing class and include it in your
plugin. See the plugin framework guide to creating plugin module instances.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
Default
false
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred. I.e. the identifier of the web resource.
name
The human-readable name of the plugin module. I.e. the human-readable name of the web
resource.
The
plugin
key
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
Description
Default
Elements
Name
Required
description
The description of the plugin module. The 'key' attribute can be specified to declare a localisation
key for the value instead of text in the element body. I.e. the description of the resource.
resource
A resource for this plugin module. This element may be repeated. A 'resource' is a non-Java file that
a plugin may need in order to operate. Refer to Adding Plugin and Module Resources for details on
defining a resource.Currently, supported file types are .css and .js.
For web resources, the type attribute must be 'download'.
N/A
dependency
Dependencies for the web resource module. A web resource can depend on other web resource(s)
to be available. Dependencies are defined in the format 'pluginKey:webResourceKey' e.g.
<dependency>com.atlassian.auiplugin:ajs</dependency>
Note: This element is only available in Plugin Framework 2.2 and later.
N/A
context
Use this element to include web resources like CSS and JavaScript on all screens of a specific type
in the application. See below.
Note: This element is only available in Plugin Framework 2.5 and later.
transformation
Use this element to make a particular transformer available to the web resource in the plugin.
Example:
For a complete description, please refer to the page on Web Resource Transformer Plugin Modules
.
Note: This element is only available in Plugin Framework 2.5 and later.
condition
Use this element to define when this web resource should display or not. See Web Item Conditions
for more information.
Note: This element is only available in Plugin Framework 2.7 or later.
Example
Here is an example atlassian-plugin.xml file containing a single web resource:
Referring to Web Resources
In your plugin, you need to refer to a WebResourceManager and call the requireResource() method. The reference to
WebResourceManager can be injected into your constructor:
Web Resource Contexts
In version 2.5 and later of the Plugin Framework, you can automatically include web resources like CSS and JavaScript on all screens of a
specific type in the application. These are called 'web resource contexts'. The currently available contexts are:
Context
Description
atl.general
Everywhere except administration screens
atl.admin
Administration screens. Use with care because poorly formed CSS or JavaScript can prevent access to administering the
application.
atl.userprofile
User profile screens.
atl.popup
Browser pop-up windows. This will open a new window for things like OAuth authorisation, and similar purposes.
The above contexts are applicable to all Atlassian applications. In addition to these application-independent contexts, each Atlassian
application can also supply its own application-specific contexts.
Example: To configure your web resource to be included in every page (both administration and non-administration pages), add <context>
child elements to your <web-resource> element in your atlassian-plugin.xml:
Using web resource contexts allows you to provide plugins that dynamically create HTML using JavaScript on any page in the application.
For example, the Confluence Content Navigation Plugin includes a snippet of JavaScript on every page in the application, which listens for a
particular keyboard shortcut to open a little search box on top the Confluence UI.
Introducing new contexts
If your plugin adds a number of screens to the application, you may find it useful to introduce a new web resource context for your plugin that
your plugin web resources (or any other plugin web resource) can hook into, to be automatically included on these screens.
To introduce a new context in your plugin Velocity templates, you can call the requireResourcesForContext() method on the
WebResourceManager object from your Velocity templates:
This will include any resource in the page that specifies a context like this in its definition:
<context>com.acme.plugin.fancy-context</context>.
We recommend that you namespace your new contexts in this way so as not to clash with any future contexts in the applications themselves
or in other plugins.
Batched Mode
The default mode for serving web resources in Plugin Framework 2.2 is batched mode. Batched mode refers to the serving of multiple plugin
resources (of the same type) in one request. For example, the two scriptaculous web resources defined above would be served in one
request, containing both scriptaculous.js and effects.js. Hence, batching reduces the number of HTTP requests that web browsers need to
make to load a web page.
URLs for batched resources are in the following format:
For the above scriptaculous example, the following code will be inserted in the header of the page:
Non-Batched Mode
Prior to Plugin Framework 2.2, each resource defined was served separately. To revert to this non-batched mode, you can either
use the system property plugin.webresource.batching.off=true to turn off batching system wide
or define a 'batch' parameter on each resource like so:
URLs for non batched resources are in the following format:
For the above scriptaculous example with batching turned off, the following code will be inserted in the header of the page:
Transforming Web Resources
Transformers are only available in Plugin Framework 2.5 and later.
The plugin framework provides web resource transformers that you can use to manipulate static web resources before they are batched and
delivered to the browser.
To use a web resource transformer, you need the following elements in your atlassian-plugin.xml file:
The transformer module: A <web-resource-transformer> element, defining the transformer plugin module. This module can
be in the same plugin as the web resource, or in a different plugin.
Transformation elements in the web resource module: A <transformation> element and its child <transformer> element
inside the <web-resource> block, making a particular transformer available to the web resource in the plugin.
For a complete description and example, please refer to the page on Web Resource Transformer plugin modules.
Notes
Since the resources are returned with headers that tell the browser to cache the content indefinitely, during development, you may
need to hold down the "shift" key while reloading the page to force the browser to re-request the files.
Web Resource Contexts in Confluence
In Confluence 2.10 and later, you can automatically include web resources like CSS and JavaScript on all screens of a specific type in the
application. These are called 'web resource contexts'. Above we described the generic contexts supplied by the Atlassian Plugin Framework
for use across all Atlassian applications.
In addition to the generic contexts described above, Confluence provides the following Confluence-specific contexts:
Context
Description
main
Everywhere except administration screens
admin
Administration screens. Use with care because poorly formed CSS or JavaScript can prevent access to administer
Confluence.
dashboard
Dashboard
editor
Anywhere an editor appears (fixed in 3.1 to work in comment editor).
page
Any page-related screen like view, edit, attachments, info; but not blog post, space or global screens.
blogpost
Any blog-related screen like view, edit, attachments, info; not page, space or global screens.
space
Any space-related screen, like those found in the top section of the Browse menu.
Technical note: the 'page', 'blogpost' and 'space' contexts correspond to the usage of the page.vmd, blogpost.vmd and space.vmd
decorators in Confluence.
Example: To configure your web resource to be included in the 'space' and 'page' contexts, add <context> child elements to the
<web-resource> element in your atlassian-plugin.xml:
<web-resource name="Resources" key="resources">
<resource name="foo.js" type="download" location="resources/foo.js">
</resource>
<context>space</context>
<context>page</context>
</web-resource>
Introducing New Contexts in Confluence
If your plugin adds a number of screens to Confluence, it might be annoying to put many #requireResource() declarations in each
Velocity template. An alternative is to introduce a new web resource context for your plugin which your plugin web resources (or any other
plugin web resource) can hook into, to be automatically included on these screens.
To introduce a new context in your plugin Velocity templates, you can call the #requireResourcesForContext() Velocity macro:
#requireResourcesForContext("com.acme.plugin.fancy-context")
This will include any resource in the page that specifies a context like this in its definition:
<context>com.acme.plugin.fancy-context</context>.
We recommend that you namespace your new contexts in this way so as not to clash with any future contexts in Confluence or other plugins.
RELATED TOPICS
Adding Plugin and Module Resources
Including Javascript and CSS resources
Writing Confluence Plugins
Installing a Plugin
Information sourced from Plugin Framework documentation
Web UI Modules
Available:
Confluence 2.2 and later
Web UI plugin modules allow you to insert links, tabs and sections of links into the Confluence web interface. They're not much use on their
own, but when combined with XWork-WebWork Plugins they become a powerful way to add functionality to Confluence.
On this page:
Sections and Items
Locations
Web Section Definition
Web Item Definition
Q and A
How do I make use of sections or web items in my own themes?
Can I create new locations for web UI plugins in my own themes?
If I create a Web Item that links to my custom action, how do I make it appear in the same tabs/context as the other items in
that location?
My web UI link isn't appearing when I use the Adaptavist Theme Builder plugin - why?
The breadcrumb trail for my web UI administration/space administration/tab plugin is showing the class name - how do I fix
it?
Sections and Items
Web UI plugins can consist of two kinds of plugin modules:
Web Item modules define links that are to be displayed in the UI at a particular location.
Web Section modules define a collection of links to be displayed together, in a 'section'.
Web items and web sections (referred to collectively as 'web fragments') may be displayed in a number of different ways, depending on the
location of the fragment and the theme under which it is being displayed.
Locations
In a number of places in the Confluence UI, there are lists of links representing operations relevant to the content being viewed.
Please be aware that the Descriptions below relate to the default Confluence theme. Bold text used in each description relates to a
component on the product interface.
These are the locations that you can customise:
Location key
Themeable?
Sectioned?
Description
Availability
system.content.action
The menu items on the Tools drop down menu available on pages
and blogs. The sections of this menu include primary, marker,
secondary and modify.
2.8
system.attachment
The links on the right of an Attachments list
2.8
system.comment.action
The links within each comment listed at the end of pages and
blogs. The sections include the primary section on the lower-left of
a comment (i.e. the Edit, Remove and Reply links) and the
secondary section on the lower-right (i.e. the Permanent link icon).
Note you MUST select a section e.g. write
"system.comment.action/primary" or
"system.comment.action/secondary"
2.8
system.content.metadata
The small icons to the left of the page metadata ("Added by Mary,
last edited by John") for attachments and permissions.
3.0
system.editor.action
Buttons in the wiki markup editor toolbar. The insert link, image
and macro buttons are in the 'insert' section.
3.1
Pages, blogs,
comments
Users
system.profile
The tabs above user profile views
2.2
system.profile.view
These links are only visible to Confluence administrators and
appear either beneath views of user profiles without personal
spaces or beneath their own profile view. For example, Administer
User
2.9
system.user
The menu items on the 'username' drop down menu available in
the top bar of all pages. The sections of this menu include
user-preferences, user-content and user-operations
2.8
system.labels
The View sub-categories of the global All Labels / Popular
Labels area
2.2
system.space.labels
The View sub-categories of the Labels tab area
2.2
system.content.add
The menu items in the Add drop down menu available in pages,
blogs and areas of the Space Admin and other Browse Space
tabs. The sections of this menu include space and page
2.8
system.space
The Space Admin and other Browse Space tabs
2.2
system.space.actions
In versions of Confluence prior to and including version 2.9, these
action icons appear in the top-right of most space-related views.
However, from Confluence 2.10, this location key has been
deprecated and has been superseded by 'system.content.add'
2.2
system.space.admin
The links in the left-hand menu of the Space Admin tab area
2.2
system.space.advanced
The links in the left-hand menu of the Advanced tab area
2.2
system.space.pages
The View sub-categories of the Pages tab area
2.2
system.dashboard
Links on the lower-left of the default global dashboard, below the
Spaces list.
2.10.2
system.browse
The global section of the Browse menu. This section appears
below the 'system.space.admin' options when inside a space.
2.8
The links in the left-hand menu of the global Administration
Console
2.2
Labels
Spaces
Global
Administration
Console
system.admin
Those locations marked as being 'themeable' can be moved around, reformatted or omitted by Theme Module. The descriptions
above refer to where they are located in the default theme.
Locations marked as being 'sectioned' require that web items be grouped under web sections. In sectioned locations, web items that
are not placed under a section will not be displayed.
It is possible for themes to make any themeable locations sectioned, even when the default theme does not. We do not recommend
this, as it would mean any plugin taking advantage of this would only be compatible with a particular theme.
Theme Compatibility
Themes based on Confluence versions prior to 2.2 will continue to function with Confluence 2.2, but will not be able to
display any custom Web UI fragments until they are updated.
Web Section Definition
You may choose to create your own web sections or add to Confluence's predefined ones, if it makes logical sense to do that.
Here is a sample atlassian-plugin.xml fragment for a web section:
<web-section key="mail" name="Mail" location="system.space.admin" weight="300">
<label key="space-mail" />
<condition
class="com.atlassian.confluence.plugin.descriptor.web.conditions.NotPersonalSpaceCondition"/>
</web-section>
Here is another sample:
<web-section key="page" name="Add Page Content" location="system.content.add" weight="200">
<label key="page.word" />
</web-section>
The above example will create a new section on the 'Add' menu. You can then add a web item in the section. The location of this section
depends on the relative weight compared to the other sections that have already been defined by Confluence or by other installed plugins.
Take a look at the full configuration of Web Section plugin modules.
The diagrams below illustrate the web sections available in the Confluence dropdown menus.
Web sections for location system.content.action
Web sections for location system.content.add
Web Item Definition
Here's a sample atlassian-plugin.xml fragment for a web item:
<web-item key="spacelogo" name="Space Logo" section="system.space.admin/looknfeel" weight="40">
<label key="configure.space.logo" />
<link>/spaces/configurespacelogo.action?key=$space.key</link>
<icon height="16" width="16">
<link>/images/icons/logo_add_16.gif</link>
</icon>
<condition
class="com.atlassian.confluence.plugin.descriptor.web.conditions.NotPersonalSpaceCondition"/>
</web-item>
Take a look at the full configuration of Web Item plugin modules.
Q and A
How do I make use of sections or web items in my own themes?
Take a look at how they are used in the default themes, you should be able to get a good idea of the necessary code. For example, here is
some sample code from space.vmd:
#set ($webInterfaceContext = $action.webInterfaceContext)
#foreach ($item in $action.webInterfaceManager.getDisplayableItems("system.space",
$webInterfaceContext))
<li><a href="$item.link.getDisplayableUrl($req, $webInterfaceContext)" #if ($context ==
$item.key) class="current" #end>
$item.label.getDisplayableLabel($req, $webInterfaceContext)
</a></li>
#end
Can I create new locations for web UI plugins in my own themes?
Yes. Just pick a new key for the location or section parameters of your plugin modules. By convention, you should probably use the
standard 'inverted domain name' prefix so as not to clash with anyone else's plugins. We reserve all system.* locations for Confluence's
core use.
Once again, however, we don't recommend this as you end up with plugins that are only useful in your own themes. Try to at least provide an
alternative set of UI modules for people who are using other themes and still want to access the same functionality. You could, for example,
define alternative UI plugin modules that placed your functions in Confluence's standard locations, but have a <condition> that disabled them
in favour of your custom locations if your theme was installed.
If I create a Web Item that links to my custom action, how do I make it appear in the same tabs/context as the other items in that location?
The best way is to look at the .vm file of one of the existing items in that location. You are most interested in the #applyDecorator
directive being called from that file. For example viewpage.vm, which defines the "View" tab in the system.page location has the following
#applyDecorator directive:
#applyDecorator("root")
#decoratorParam("helper" $action.helper)
#decoratorParam("mode" "view")
#decoratorParam("context" "page")
<!-- some stuff... -->
#end
If you were writing a plugin that was destined to be added as another item in the page tabs, your Velocity file for that action would also have
to have a similar decorator directive around it:
#applyDecorator("root")
#decoratorParam("helper" $action.helper)
#decoratorParam("mode" "myPluginKey")
#decoratorParam("context" "page")
<!-- some stuff... -->
#end
Note that you should put your Web Item's plugin key as the 'mode'. This way, Confluence will make sure that the correct tab is highlighted as
the active tab when people are viewing your action.
In some cases, such as the browse space tabs, you may have to use 'context' instead of 'mode'.
My web UI link isn't appearing when I use the Adaptavist Theme Builder plugin - why?
Theme Builder uses completely customisable navigation and as such can't automatically display web UI links because this would likely lead
to duplication of many other, more common links.
You can, however use the {menulink} macro to insert any web UI link using the following notation:
{menulink:webui|location=XXXX|key=YYYY}webui link{menulink}
Theme Builder 2.0.8 and above now supports a growing number of third party plugins as standard - for more information see the online
documentation. If you have a publicly available plugin and want an in-built menulink location for it, please contact Adaptavist.
The breadcrumb trail for my web UI administration/space administration/tab plugin is showing the class name — how do I fix it?
In the atlassian-plugin.xml:
<!--Make sure each name is unique-->
<resource type="i18n" name="i18n-viewreview"
location="resources/ViewReviewAction" />
In the java:
//in an action
I18NBean i18NBean = getI18n();
//or in a macro or other sort of plugin
ThemeHelper helper = this.getHelper();
ConfluenceActionSupport action = (ConfluenceActionSupport) helper.getAction();
Locale locale = action.getLocale();
I18NBean i18nBean = i18NBeanFactory.getI18NBean(locale);
//and
public void setI18NBeanFactory(I18NBeanFactory i18NBeanFactory)
{
this.i18NBeanFactory = i18NBeanFactory;
}
Use a normal properties file and locate it as follows:
If we're talking about actions:
The properties file with the same name as the relevant action can go in the same directory as the action. So, if you had XYZAction.java,
then XYZAction.properties could live in the same directory. And you would not have to do anything in the atlassian-plugin.xml
file.
If you don't want it to live there, or if you're not talking about an action:
Define a resource in the atlassian-plugin.xml and tell it to live wherever you want. The standard is resources.
In the source: etc/resources
In the jar: resources/
The property that handles the breadcrumb has to be the fully qualified name of the class plus .action.name
So, for a SharePointAdmin property you might use:
com.atlassian.confluence.extra.sharepoint.SharePointAdmin.action.name=SharePoint Admin
RELATED TOPICS
Web Section Plugin Module
Web Item Plugin Module
Writing Confluence Plugins
Installing a Plugin
Web Item Plugin Module
On this page:
Purpose of this Module Type
Configuration
Attributes
Elements
Label Elements
Tooltip Elements
Link Elements
Icon Elements
Param Elements
Context-provider Element
Condition and Conditions Elements
Example
Notes about Web Items in Confluence
Link elements
Purpose of this Module Type
Web Item plugin modules allow plugins to define new links in application menus.
Configuration
The root element for the Web Item plugin module is web-item. It allows the following attributes and child elements for configuration:
Attributes
Name
Required
Description
class
The class which implements this plugin module. The class you need to provide depends on the
module type. For example, Confluence theme, layout and colour-scheme modules can use classes
already provided in Confluence. So you can write a theme-plugin without any Java code. But for
macro and listener modules you need to write your own implementing class and include it in your
plugin. See the plugin framework guide to creating plugin module instances.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
Default
false
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred.
name
The human-readable name of the plugin module. Used only in the plugin's administrative user
interface.
section
Location into which this web item should be placed. For non-sectioned locations, this is just the
location key. For sectioned locations it is the location key, followed by a slash ('/'), and the name of
the web section in which it should appear.
N/A
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
weight
Determines the order in which web items appear. Items are displayed top to bottom or left to right in
order of ascending weight. The 'lightest' weight is displayed first, the 'heaviest' weights sink to the
bottom. The weights for most applications' system sections start from 100, and the weights for the
links generally start from 10. The weight is incremented by 10 for each in sequence so that there is
ample space to insert your own sections and links.
1000
Elements
The table summarises the elements. The sections below contain further information.
Name
Required
Description
Default
condition
Defines a condition that must be satisfied for the web item to be displayed. If you want to 'invert' a
condition, add an attribute 'invert="true"' to it. The web item will then be displayed if the condition
returns false (not true).
N/A
conditions
Defines the logical operator type to evaluate its condition elements. By default 'AND' will be used.
AND
context-provider
Allows dynamic addition to the velocity context available for various web item elements (in XML
descriptors only). Currently only one context-provider can be specified per web item and section.
description
The description of the plugin module. The 'key' attribute can be specified to declare a localisation
key for the value instead of text in the element body. I.e. the description of the web item.
icon
Defines an icon to display with or as the link. Note: In some cases the icon element is required.
Try adding it if your web section is not displaying properly.
N/A
label
Is the i18n key that will be used to look up the textual representation of the link.
N/A
link
Defines where the web item should link to. The contents of the link element will be rendered using
Velocity, allowing you to put dynamic content in links. For more complex examples of links, see
below.
N/A
param
Parameters for the plugin module. Use the 'key' attribute to declare the parameter key, then
specify the value in either the 'value' attribute or the element body. This element may be repeated.
An example is the configuration link described in Adding a Configuration UI for your Plugin. This is
handy if you want to use additional custom values from the UI.
N/A
resource
A resource for this plugin module. This element may be repeated. A 'resource' is a non-Java file
that a plugin may need in order to operate. Refer to Adding Plugin and Module Resources for
details on defining a resource.
N/A
tooltip
Is the i18n key that will be used to look up the textual mouse-over text of the link.
N/A
Label Elements
Label elements may contain optional parameters, as shown below:
The parameters allow you to insert values into the label using Java's MessageFormat syntax.
Parameter names must start with param and will be mapped in alphabetical order to the substitutions in the format string. I.e.
param0 is {0}, param1 is {1}, param2 is {2}, etc.
Parameter values are rendered using Velocity, allowing you to include dynamic content.
Tooltip Elements
Tooltip elements have the same attributes and parameters as the label elements. See above.
Link Elements
Link elements may contain additional information:
The linkId is optional, and provides an XML id for the link being generated.
The absolute is optional and defaults to false unless the link starts with http:// or https://
The body of the link element is its URL. The URL is rendered with Velocity, so you can include dynamic information in the link. For example,
in Confluence, the following link would include the page ID:
Icon Elements
Icon elements have a height and a width attribute. The location of the icon is specified within a link element:
Param Elements
Param elements represent a map of key/value pairs, where each entry corresponds to the param elements attribute: name and value
respectively.
The value can be retrieved from within the Velocity view with the following code, where $item is a WebItemModuleDescriptor:
If the value attribute is not specified, the value will be set to the body of the element. I.e. the following two param elements are equivalent:
Context-provider Element
Available:
Atlassian Plugins 2.5, Confluence 2.5, Bamboo 3.0, JIRA 4.2 and later
The context-provider element adds to the Velocity context available to the web section and web item modules. You can add what you need to
the context, to build more flexible section and item elements. Currently only one context-provider can be specified per module. Additional
context-providers are ignored.
The context-provider element must contain a class attribute with the fully-qualified name of a Java class. The referenced class:
must implement com.atlassian.plugin.web.ContextProvider, and
will be auto-wired by Spring before any additions to the Velocity context.
For example, the following context-provider will add historyWindowHeight and filtersWindowHeight to the context.
In the following example, HeightContextProvider extends AbstractJiraContextProvider, which is only available in JIRA and
happens to implement ContextProvider. The AbstractJiraContextProvider conveniently extracts the User and JiraHelper from
the context map, which you would otherwise have to do manually.
The above HeightContextProvider can be used by nesting the following element in a web item module.
The newly added context entries historyWindowHeight and filtersWindowHeight can be used in the XML module descriptors just
like normal velocity context variables, by prefixing them with the dollar symbol ($):
Condition and Conditions Elements
Conditions can be added to the web section, web item and web panel modules, to display them only when all the given conditions are true.
Condition elements must contain a class attribute with the fully-qualified name of a Java class. The referenced class:
must implement com.atlassian.plugin.web.Condition, and
will be auto-wired by Spring before any condition checks are performed.
Condition elements can take optional parameters. These parameters will be passed in to the condition's init() method as a map of string
key/value pairs after autowiring, but before any condition checks are performed. For example:
To invert a condition, add the attribute 'invert="true"' to the condition element. This is useful where you want to show the section if a certain
condition is not satisfied.
Conditions elements are composed of a collection of condition/conditions elements and a type attribute. The type attribute defines what
logical operator is used to evaluate its collection of condition elements. The type can be one of AND or OR.
For example: The following condition is true if the current user is a system administrator OR a project administrator:
Example
Here is an example atlassian-plugin.xml file containing a single web item:
Notes about Web Items in Confluence
Link elements
Here is another example of a Link elements containing additional information:
<link linkId="editPageLink"
accessKey="$helper.action.getTextStrict('navlink.edit.accesskey')">/pages/editpage.action?pageId=$helper.page.id</l
The accessKey is optional and provides an access key for the link being generated.
There is no standard way for Confluence to display a web item.
Depending on where the item is being displayed, some information in the configuration may be ignored. For example,
themes may choose not to display the icon, or may choose to display only the icon. Similarly, the linkId and accessKey
attributes are only used in some locations.
RELATED TOPICS
Web UI Modules
Web Section Plugin Module
Web Resource Module
Writing Confluence Plugins
Information sourced from Plugin Framework documentation
Web Section Plugin Module
On this page:
Purpose of this Module Type
Configuration
Attributes
Elements
Label Elements
Tooltip Elements
Param Elements
Context-provider Element
Condition and Conditions elements
Example
Purpose of this Module Type
Web Section plugin modules allow plugins to define new sections in application menus. Each section can contain one or more links. To insert
the links themselves, see the Web Item Plugin Module.
Configuration
The root element for the Web Section plugin module is web-section It allows the following attributes and child elements for configuration:
Attributes
Name
Required
Description
class
The class which implements this plugin module. The class you need to provide depends on the
module type. For example, Confluence theme, layout and colour-scheme modules can use classes
already provided in Confluence. So you can write a theme-plugin without any Java code. But for
macro and listener modules you need to write your own implementing class and include it in your
plugin. See the plugin framework guide to creating plugin module instances.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
Default
false
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred.
name
The human-readable name of the plugin module. Only used in the plugin's administrative user
interface.
location
Location into which this web item should be placed.
N/A
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
weight
Determines the order in which web items appear. Items are displayed top to bottom or left to right in
order of ascending weight. The 'lightest' weight is displayed first, the 'heaviest' weights sink to the
bottom. The weights for most applications' system sections start from 100, and the weights for their
links generally start from 10. The weight is incremented by 10 for each in sequence so that there is
ample space to insert your own sections and links.
N/A
Elements
The table summarises the elements. The sections below contain further information.
Name
Required
Description
Default
condition
Defines a condition that must be satisfied for the web item to be displayed. If you want to 'invert' a
condition, add an attribute 'invert="true"' to it. The web item will then be displayed if the condition
returns false (not true).
N/A
conditions
Defines the logical operator type used to evaluate the condition elements. By default 'AND' will be
used.
AND
context-provider
Allows dynamic addition to the Velocity context available for various web item elements (in XML
descriptors only). Currently only one context-provider can be specified per web item and section.
N/A
description
The description of the plugin module. The 'key' attribute can be specified to declare a localisation
key for the value instead of text in the element body. Use this element to describe the section.
label
Is the i18n key that will be used to look up the textual representation of the link.
N/A
param
Parameters for the plugin module. Use the 'key' attribute to declare the parameter key, then
specify the value in either the 'value' attribute or the element body. This element may be repeated.
An example is the configuration link described in Adding a Configuration UI for your Plugin.
Defines a key/value pair available from the web item. This is handy if you want to use additional
custom values from the UI.
N/A
resource
A resource for this plugin module. This element may be repeated. A 'resource' is a non-Java file
that a plugin may need in order to operate. Refer to Adding Plugin and Module Resources for
details on defining a resource.
N/A
tooltip
Is the i18n key that will be used to look up the textual mouse-over text of the link.
N/A
Label Elements
Label elements may contain optional parameters, as shown below:
The parameters allow you to insert values into the label using Java's MessageFormat syntax.
Parameter names must start with param and will be mapped in alphabetical order to the substitutions in the format string. I.e.
param0 is {0}, param1 is {1}, param2 is {2}, etc.
Parameter values are rendered using Velocity, allowing you to include dynamic content.
Tooltip Elements
Tooltip elements have the same attributes and parameters as the label elements. See above.
Param Elements
Param elements represent a map of key/value pairs, where each entry corresponds to the param elements attribute: name and value
respectively.
The value can be retrieved from within the Velocity view with the following code, where $item is a WebItemModuleDescriptor:
If the value attribute is not specified, the value will be set to the body of the element. I.e. the following two param elements are equivalent:
Context-provider Element
Available:
Atlassian Plugins 2.5, Confluence 2.5, Bamboo 3.0, JIRA 4.2 and later
The context-provider element adds to the Velocity context available to the web section and web item modules. You can add what you need to
the context, to build more flexible section and item elements. Currently only one context-provider can be specified per module. Additional
context-providers are ignored.
The context-provider element must contain a class attribute with the fully-qualified name of a Java class. The referenced class:
must implement com.atlassian.plugin.web.ContextProvider, and
will be auto-wired by Spring before any additions to the Velocity context.
For example, the following context-provider will add historyWindowHeight and filtersWindowHeight to the context.
In the following example, HeightContextProvider extends AbstractJiraContextProvider, which is only available in JIRA and
happens to implement ContextProvider. The AbstractJiraContextProvider conveniently extracts the User and JiraHelper from
the context map, which you would otherwise have to do manually.
The above HeightContextProvider can be used by nesting the following element in a web item module.
The newly added context entries historyWindowHeight and filtersWindowHeight can be used in the XML module descriptors just
like normal velocity context variables, by prefixing them with the dollar symbol ($):
Condition and Conditions elements
Conditions can be added to the web section, web item and web panel modules, to display them only when all the given conditions are true.
Condition elements must contain a class attribute with the fully-qualified name of a Java class. The referenced class:
must implement com.atlassian.plugin.web.Condition, and
will be auto-wired by Spring before any condition checks are performed.
Condition elements can take optional parameters. These parameters will be passed in to the condition's init() method as a map of string
key/value pairs after autowiring, but before any condition checks are performed. For example:
To invert a condition, add the attribute 'invert="true"' to the condition element. This is useful where you want to show the section if a certain
condition is not satisfied.
Conditions elements are composed of a collection of condition/conditions elements and a type attribute. The type attribute defines what
logical operator is used to evaluate its collection of condition elements. The type can be one of AND or OR.
For example: The following condition is true if the current user is a system administrator OR a project administrator:
Example
Here is an example atlassian-plugin.xml file containing a single web section, using a condition that will be available in JIRA:
RELATED TOPICS
Web UI Modules
Web Item Plugin Module
Web Resource Module
Writing Confluence Plugins
Information sourced from Plugin Framework documentation
Web Panel Plugin Module
Available:
Confluence 3.3 and later.
Changed:
Web-panel locations are available only in Confluence 4.0 and later.
On this page:
Purpose of this Module Type
Configuration
Attributes
Elements
Resource Element
Context-provider Element
Condition and Conditions Elements
Web Panel Examples
Web Panel Locations in Confluence
Purpose of this Module Type
Web Panel plugin modules allow plugins to define panels, or sections, on an HTML page. A panel is a set of HTML that will be inserted into a
page.
Configuration
The root element for the Web Panel plugin module is web-panel. It allows the following attributes and child elements for configuration:
Attributes
Name
Required
Description
class
The class which implements this plugin module and which is responsible for providing the web
panel's HTML. In most cases you will not need to provide a custom class to generate the content,
as you can simply point to a static HTML file or a (Velocity) template. See the plugin framework
guide to creating plugin module instances. If you omit this attribute, you MUST provide a resource
element and vice versa, to ensure there is always exactly one source for the web panel's content.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
Default
false
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred.
name
The human-readable name of the plugin module. Used only in the plugin's administrative user
interface.
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
weight
Determines the order in which web panels appear. Web panels are displayed top to bottom or left
to right in order of ascending weight. The 'lightest' weight is displayed first, the 'heaviest' weights
sink to the bottom. The weights for most applications' system sections start from 100, and the
weights for the links generally start from 10. The weight is incremented by 10 for each in sequence
so that there is ample space to insert your own panels.
1000
location
The location in the host application where the web panel must be rendered. Note that every host
application declares its own set of web panel plugin points. Currently a web panel can only be
associated with a single location.
Elements
The table summarises the elements. The sections below contain further information.
Name
Required
Description
Default
condition
Defines a condition that must be satisfied for the web panel to be displayed. If you want to 'invert'
a condition, add an attribute 'invert="true"' to it. The web item will then be displayed if the condition
returns false (not true).
N/A
conditions
Defines the logical operator type to evaluate its condition elements. By default 'AND' will be used.
AND
context-provider
Allows dynamic addition to the Velocity context available for various web panel elements (in XML
descriptors only). Currently only one context-provider can be specified per web panel.
label
Is the i18n key that will be used to look up the textual representation of the link.
N/A
param
Parameters for the plugin module. Use the 'key' attribute to declare the parameter key, then
specify the value in either the 'value' attribute or the element body. This element may be repeated.
An example is the configuration link described in Adding a Configuration UI for your Plugin. This is
handy if you want to use additional custom values from the UI.
N/A
description
The description of the plugin module. The 'key' attribute can be specified to declare a localisation
key for the value instead of text in the element body. I.e. the description of the web panel.
resource
A resource element is used to provide a web panel with content. It can be used in a way similar to
normal resources, using the resource's location attribute to point to a static HTML file or (Velocity)
template file that is provided by the plugin's JAR file. To differentiate between static HTML and
Velocity templates that need to be rendered, always specify the type attribute. See the examples
further down on this page. It is also possible to embed the contents (both static HTML or velocity)
directly in the atlassian-plugin.xml file by encoding it in the resource element's body and
then omitting the location attribute. Note that if you omit the resource element you MUST
provide the module descriptor's class attribute, and vice versa, to ensure there is always exactly
one source for the web panel's content.
N/A
Condition and Conditions Elements
Conditions can be added to the web section, web item and web panel modules, to display them only when all the given conditions are true.
Condition elements must contain a class attribute with the fully-qualified name of a Java class. The referenced class:
must implement com.atlassian.plugin.web.Condition, and
will be auto-wired by Spring before any condition checks are performed.
Condition elements can take optional parameters. These parameters will be passed in to the condition's init() method as a map of string
key/value pairs after autowiring, but before any condition checks are performed. For example:
To invert a condition, add the attribute 'invert="true"' to the condition element. This is useful where you want to show the section if a certain
condition is not satisfied.
Conditions elements are composed of a collection of condition/conditions elements and a type attribute. The type attribute defines what
logical operator is used to evaluate its collection of condition elements. The type can be one of AND or OR.
For example: The following condition is true if the current user is a system administrator OR a project administrator:
Context-provider Element
Available:
Atlassian Plugins 2.5, Confluence 2.5, Bamboo 3.0, JIRA 4.2 and later
The context-provider element adds to the Velocity context available to the web section and web item modules. You can add what you need to
the context, to build more flexible section and item elements. Currently only one context-provider can be specified per module. Additional
context-providers are ignored.
The context-provider element must contain a class attribute with the fully-qualified name of a Java class. The referenced class:
must implement com.atlassian.plugin.web.ContextProvider, and
will be auto-wired by Spring before any additions to the Velocity context.
For example, the following context-provider will add historyWindowHeight and filtersWindowHeight to the context.
In the following example, HeightContextProvider extends AbstractJiraContextProvider, which is only available in JIRA and
happens to implement ContextProvider. The AbstractJiraContextProvider conveniently extracts the User and JiraHelper from
the context map, which you would otherwise have to do manually.
The above HeightContextProvider can be used by nesting the following element in a web item module.
The newly added context entries historyWindowHeight and filtersWindowHeight can be used in the XML module descriptors just
like normal velocity context variables, by prefixing them with the dollar symbol ($):
Label Elements
Label elements may contain optional parameters, as shown below:
The parameters allow you to insert values into the label using Java's MessageFormat syntax.
Parameter names must start with param and will be mapped in alphabetical order to the substitutions in the format string. I.e.
param0 is {0}, param1 is {1}, param2 is {2}, etc.
Parameter values are rendered using Velocity, allowing you to include dynamic content.
Param Elements
Param elements represent a map of key/value pairs, where each entry corresponds to the param elements attribute: name and value
respectively.
The value can be retrieved from within the Velocity view with the following code, where $item is a WebItemModuleDescriptor:
If the value attribute is not specified, the value will be set to the body of the element. I.e. the following two param elements are equivalent:
Resource Element
Unless the module descriptor's class attribute is specified, a web panel will contain a single resource child element that contains the
contents of the web panel. This can be plain HTML, or a (Velocity) template to provide dynamic content.
A web panel's resource element can either contain its contents embedded in the resource element itself, as part of the
atlassian-plugin.xml file, or it can link to a file on the classpath when the location attribute is used.
A resource element's type attribute identifies the format of the panel's content (currently "static" and "velocity" are provided by Atlassian
Plugin Framework 2.5.0 and atlassian-template-renderer 2.5.0 respectively) which allows the plugin framework to use the appropriate
com.atlassian.plugin.web.renderer.WebPanelRenderer.
Type
Description
static
Used to indicate that the web panel's contents must not be processed, but included in the page as is.
velocity
Used to indicate that the web panel contains Velocity markup that needs to be parsed.
The template rendering system is extensible. You can add custom renderers by creating plugins. For more information on this, check out the
Web Panel Renderer Plugin Module.
Web Panel Examples
The values of the location attributes in the examples below are not real. They are just illustrative of the kind of location that
Confluence, Bamboo and FishEye make available.
A web panel that contains static, embedded HTML:
A web panel that contains an embedded Velocity template:
A web panel containing a Velocity template that is on the classpath (part of the plugin's JAR file):
As mentioned previously, it is also possible to provide your own custom class that is responsible for producing the panel's HTML, by using
the descriptor's class attribute (which makes the resource element redundant):
Note that com.example.FooWebPanel MUST implement WebPanel.
Web Panel Locations in Confluence
Below are the specific locations that Confluence provides for web panels, from Confluence 4.0 onwards. In earlier versions, you will need to
manually include your web-panel.
Key
Template
Purpose
atl.userprofile
profile.vm
Follows the same naming convention as the resources for a user profile. Allows you to add a web panel to a
user profile page in Confluence.
Available only in Confluence 4.0 and later.
atl.header
header.vm
Allows you to add tags to the HTML <head> of each page in Confluence. Your web panel must only contain
tags which are valid in the header of an HTML document: meta, link, script, etc.
Available only in Confluence 4.0 and later.
atl.general
main.vmd
At the top of the HTML <body> on each page in Confluence.
Available only in Confluence 4.0 and later.
atl.editor
editor.vm
On the Confluence edit screen.
Available only in Confluence 4.0 and later.
RELATED TOPICS
Web UI Modules
Web Section Plugin Module
Web Panel Renderer Plugin Module
Web Resource Module
Writing Confluence Plugins
Information sourced from Plugin Framework documentation
Web Panel Renderer Plugin Module
Available:
Confluence 3.3 and later.
On this page:
Purpose of this Module Type
Configuration
Attributes
Writing a Custom Renderer
Known Limitations
Source Code
Purpose of this Module Type
The Web Panel Renderer plugin module allows plugins to define custom renderer engines for web panels. (Web panels are bits of HTML that
will be inserted into a page.)
Configuration
The root element for the Web Panel Renderer plugin module is web-panel-renderer. It allows the following attributes and child elements
for configuration:
Attributes
Name
Required
Description
Default
class
The class which implements com.atlassian.plugin.web.renderer.WebPanelRenderer. This class is
responsible for turning a web panel's content into proper HTML. See the plugin framework guide to
creating plugin module instances.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
false
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred.
name
The human-readable name of the plugin module. Used only in the plugin's administrative user
interface.
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
Writing a Custom Renderer
To create your own renderer you should create a class that implements com.atlassian.plugin.web.renderer.WebPanelRenderer.
As an example we will create a plugin for the Atlassian Reference Application (version 2.5.0 or higher). We will create a web panel template
renderer for FreeMarker templates, which is a format that is not supported by the Atlassian Plugin Framework out of the box. We will then
also add a web panel that uses a FreeMarker template.
1. Using the Atlassian Plugin SDK, create a new plugin for the Reference Application and make sure the generated pom.xml file uses
version 2.5.0 or higher:
$ atlas-create-refapp-plugin
2. Add the FreeMarker library to the Maven dependencies:
pom.xml
3. Create your renderer class:
Note how the WebPanelRenderer interface declares two render methods: one that takes the name of a template file and one that
takes the whole template as a String. In this example we have only implemented the former method. The latter is left as an exercise
for you. The consequence of this is that we will not be able to embed our FreeMarker content in atlassian-plugin.xml.
4. Add the new renderer to atlassian-plugin.xml:
atlassian-plugin.xml
5. Add a web panel to the Reference Application's administration page that uses this new renderer:
atlassian-plugin.xml
6.
6. Add your FreeMarker template:
src/main/resources/templates/mytemplate.ftl
7. Start up the Reference Application using the command: $ atlas-mvn refapp:run)
8. Go to: http://localhost:5990/refapp/admin
Known Limitations
You may have noticed how the configuration for our FreeMarker's template loader uses a freemarker.cache.ClassTemplateLoader
instance which expects templates to be on the classpath. To do this, FreeMarker's ClassTemplateLoader constructor takes a Class
instance and then calls Class.getResource() when it needs to load a template.
In our example we use FreeMarkerWebPanelRenderer.class, which means that our renderer is limited to rendering templates that live
in its own plugin JAR file. This is sufficient for this tutorial which has the renderer, the web panel and the template all in the same plugin.
However, it would not work when the renderer is shared with other plugins and needs to render a template that lives in another plugin JAR.
If you want to build a shared FreeMarker renderer this way, you would have to implement your own
freemarker.cache.TemplateLoader. Instead of taking a Class instance, your template loader would take the ClassLoader that is
returned by plugin.getClassLoader().
To keep the example clear and simple, we have chosen to accept this limitation. However, note that it has been addressed properly in the full
source code that is available below.
Source Code
To access the full source code for this plugin, you can:
browse it online.
check it out from Subversion.
RELATED TOPICS
Web UI Modules
Web Section Plugin Module
Web Panel Plugin Module
Web Resource Module
Writing Confluence Plugins
Information sourced from Plugin Framework documentation
Web Resource Transformer Module
Available:
Confluence 3.4 and later.
On this page:
Purpose of this Module Type
Configuration
Attributes of web-resource-transformer
Child Elements of web-resource-transformer
Attributes of transformation
Child Elements of transformation
Attributes of transformer
Child Elements of transformer
Example
Notes
Purpose of this Module Type
Web Resource Transformer plugin modules allow you to manipulate static web resources before they are batched and delivered to the
browser. This means that you can get past the restrictions of straight JavaScript and CSS, including restrictions like no includes, no external
resource support, no CSS variables, only one JS context, and so on.
Configuration
To use a web resource transformer, you need the following elements in your atlassian-plugin.xml file:
The transformer module: A <web-resource-transformer> element, defining the transformer plugin module. This module can
be in the same plugin as the web resource, or in a different plugin.
Transformation elements in the web resource module: A <transformation> element and its child <transformer> element
inside the <web-resource> block, making a particular transformer available to the web resource in the plugin.
Below is a description of the attributes and child elements for each of the above elements.
Attributes of web-resource-transformer
Name
Required
Description
Default
class
The class which implements
com.atlassian.plugin.webresource.transformer.WebResourceTransformer. This class is responsible
for doing the resource transformation before it is served to the client. See the plugin framework
guide to creating plugin module instances.
disabled
Indicate whether the plugin module should be disabled by default (value='true') or enabled by
default (value='false').
i18n-name-key
The localisation key for the human-readable name of the plugin module.
key
The identifier of the plugin module. This key must be unique within the plugin where it is defined.
false
N/A
Sometimes, in other contexts, you may need to uniquely identify a module. Do this with the
complete module key. A module with key fred in a plugin with key com.example.modules will
have a complete key of com.example.modules:fred.
The value of this attribute must match the key attribute of the transformer element in the
web-resource.
name
The human-readable name of the plugin module.
system
Indicates whether this plugin module is a system plugin module (value='true') or not (value='false').
Only available for non-OSGi plugins.
false
Child Elements of web-resource-transformer
Name
Required
description
Description
Default
The description of the plugin module. The 'key' attribute can be specified to declare a localisation key
for the value instead of text in the element body.
Attributes of transformation
Name
Required
Description
extension
Default
All the transformers in the transformation block apply to resources with this extension.
Child Elements of transformation
Name
Required
transformer
Description
Default
Defines a transformation process.
Attributes of transformer
Name
Required
Description
Default
key
The value of this attribute must match the key attribute of the web-resource-transformer element.
(others)
You can add your own attributes as required, to pass information to your implementation of the
transformer.
Child Elements of transformer
Name
Required
(others)
Description
Default
You can add your own child elements as required, to pass information to your implementation of the
transformer.
Example
Here is an example atlassian-plugin.xml file containing a web resource and a transformer that turns a static template file into a
JavaScript variable. You could use this transformer for common components that need to use client-side templates.
The template testTemplate.txt file looks like this:
Hello world "bob"
and 'friends'
The JavaScript resulting from the transformation is:
Notes
Some information to be aware of when developing or configuring a Web Resource Transformer plugin module:
The <web-resource-transformer> module can live in the same plugin as the <web-resource> module, or in a different
module. The transformers are registered globally.
You can apply multiple transformers. Any subsequent transformer will process the result of the earlier transformation.
You can pass information to the transformer by adding arbitrary attributes and child elements to the <transformer> element in the
resource.
RELATED TOPICS
Web UI Modules
Web Item Plugin Module
Web Section Plugin Module
Web Panel Renderer Plugin Module
Web Resource Module
Writing Confluence Plugins
Information sourced from Plugin Framework documentation
Workflow Module
This set of pages describes the Workflow Plugin. This is a work in progress and is useful as:
An example of a reasonably complicated plugin, using macros, events and xwork actions which stores state as page properties and
interacts with content entity versions and permissions.
A starting point for discussion of what plugin-based workflow in Confluence might look like. A workflow implementation which made
core Confluence changes might look different.
Here's a description of the workflow model implemented by the plugin.
The workflow plugin as released in 1.4.2 does not have all the features described. It will be updated in the first 1.5DP
release.
We're interested in getting feedback – how useful does the workflow model as described seem to you?
Workflow Plugin Prototype
Introduction
This page describes a prototype Workflow Plugin for Confluence. After reading it you should be able to create a workflow description and use
it to manage a set of pages in Confluence.
The purposes of the Confluence Workflow Plugin Prototype are:
1. To provide a simple but usable workflow system for Confluence.
2. To solicit further requirements for Workflow in Confluence.
3. To demonstrate the power of the Confluence Plugin system – the workflow plugin did not require any changes to the core of
Confluence.
The feature that this does not provide is the ability of different users to see different versions of a page. This is a problem for approval
workflows, where we want an edit to remain invisible to 'ordinary' users until it has been approved.
I've also written up some ideas for a minimal Approval Workflow.
Plugin Information
You will need Java and Groovy development skills to implement this plugin. This is currently provided 'as-is' without Atlassian technical
support, but you can search for or post questions relating to it in the Developer Forums. Alternatively, the Atlassian partner Saikore now
offers paid support.
Workflow Concepts
This section describes the concepts used in building the Workflow Plugin.
Workflow Client
This is the entity whose life cycle is managed by the workflow plugin. In this implementation a client is a Confluence page. The client is
responsible for remembering which workflow it is taking part in, remembering its workflow state, and changing this state when told to by the
workflow system. A client may (and should) have other state information which is not visible to the workflow system, for instance the contents
of a Confluence page are not managed by the workflow system at all.
Workflow Type
This is the set of data which defines a workflow. A workflow type is assembled from collections of States, Operations, Triggers and Actions.
Workflow State
At any time a Workflow Client is in one (and only one) State. This state determines which Operations are available to be performed on the
client.
Operation
An Operation may be requested by the user on a Workflow Client. An Operation itself doesn't change any state, either in the workflow system
or in the Workflow Client, but simply sends a signal to the Workflow Type that this Operation has been requested on that particular Workflow
Client. It is just a description meaningful to a user, associated with a code meaningful to the Workflow Type, together with security rules to
determine when the Operation can be performed. The signals sent to the Workflow Type may cause one or more Triggers to fire. Whether an
Operation is available on a particular Client depends on the State of the client and the group membership of the current user. In addition to
Operations defined in a particular Workflow Type, all Workflow Types recognize page edit and page view operations.
Trigger
A Trigger listens for Operations, and either fires or does not fire, depending on the Operation, its internal state (if any – many simple triggers
are stateless) and its implementation. When a Trigger fires it tells the set of Actions it contains to execute.
Examples of Triggers are:
1. Fire every time you receive a particular event.
2. Fire after receiving any of a set of events.
3. Fire after receiving all of a set of events, in any order. (This requires a Trigger which can maintain internal state)
Action
An Action is a piece of code which is executed in response to the firing of a Trigger.
Some Actions interact with the Workflow System:
1. Change Workflow State of Client.
2. Create a new Trigger.
3. Remove a Trigger.
Others interact with Confluence:
1. Restrict Page Permissions
2. Remove Page Permissions restriction.
3. Send Notification to prior editor of page.
Others could interact with the contents of the page itself:
1. Add 'Draft' warning to page contents.
2.
2. Validate field values in the page contents.
Using The Prototype Confluence Workflow Plugin
Build and Install the Workflow Plugin
From you Confluence install directory, go to plugins/workflow or acccess from the Confluence source under src/etc/plugins/workflow. Build
the plugin into a JAR file.
Configure groups and permissions
Decide what groups will be involved in the workflow, create them and assign appropriate users to them. Grant suitable permissions to the
space.
Create a WorkflowType
You need to create an instance of a class which implements com.atlassian.confluence.extra.workflow.WorkflowType, and register it by
passing it to WorkflowManager.registerType().
One way to do this on a test basis is to put your workflow type in a {script} macro. The script macro can be downloaded from here. You'll
need to visit the page after restarting the server.
The example below uses a Groovy script – you could just as well use Beanshell, Jython or JRuby.
{script:groovy}
import com.atlassian.confluence.extra.workflow.*;
import com.atlassian.confluence.core.ContentPermission;
State requested = new State("test", "In Progress", "In Progress");
State readyToReview = new State("test", "Ready for review", "Ready for review");
State accepted = new State("test", "Accepted", "Accepted");
State rejected = new State("test", "Rejected", "Rejected");
def states = [DEV:requested, readyToReview, accepted, rejected];
def ops = [
new DefaultOperation([DEV:requested, rejected], [DEV:"writer"], "completed", "Submit for Review"),
new DefaultOperation([DEV:readyToReview],[DEV:"reviewer"], "accept", "Accept"),
new DefaultOperation([DEV:readyToReview],[DEV:"reviewer"], "reject", "Reject"),
];
def groups = [DEV:"writer", "reviewer", "confluence-administrator"];
def triggers = [
new SingleEventTrigger("init",
[DEV:
new StateChangeAction(requested),
new RestrictAccessToGroupAction(new
ContentPermission(ContentPermission.EDIT_PERMISSION,"writer")),
new RestrictAccessToGroupAction(new
ContentPermission(ContentPermission.VIEW_PERMISSION,"writer")),
]
),
new SingleEventTrigger(
"completed",
[DEV:
new StateChangeAction(readyToReview),
new RestrictAccessToGroupAction(new
ContentPermission(ContentPermission.EDIT_PERMISSION,"reviewer")),
new RestrictAccessToGroupAction(new
ContentPermission(ContentPermission.VIEW_PERMISSION,"reviewer")),
]
),
new SingleEventTrigger(
"accept",
[DEV:
new StateChangeAction(accepted),
new RestrictAccessToGroupAction(new
ContentPermission(ContentPermission.EDIT_PERMISSION,"empty-group")),
new RestrictAccessToGroupAction(new
ContentPermission(ContentPermission.VIEW_PERMISSION,"confluence-users"))
]
),
new SingleEventTrigger(
"reject",
[DEV:
new StateChangeAction(rejected),
new RestrictAccessToGroupAction(new
ContentPermission(ContentPermission.EDIT_PERMISSION,"writer")),
new RestrictAccessToGroupAction(new ContentPermission(ContentPermission.VIEW_PERMISSION,"writer"))
]
),
new SingleEventTrigger(
PageEditOperation.OPERATION_NAME,
[DEV:
new StateChangeAction(requested),
new RestrictAccessToGroupAction(new
ContentPermission(ContentPermission.EDIT_PERMISSION,"writer")),
new RestrictAccessToGroupAction(new ContentPermission(ContentPermission.VIEW_PERMISSION,"writer"))
]
),
];
WorkflowManager.registerType(new DefaultWorkflowType("test2","Page Review
2",states,ops,triggers,groups));
{script}
Put a {workflowtype:yourWorkflowTypeName} macro after your script, so you can see that it is properly creating the WorkflowType.
Create a Workflow Page
To make a page take part in the workflow you have just created, add the {workflow:workflowTypeName} macro to the page and hit Update.
You'll get a workflow box with the option 'Start Workflow'. Select this and the page will refresh. The workflow box will now indicate that the
page is in the starting state for that workflow type.
Monitoring Workflow
You can use the {workflowTasks} macro to display a list of all workflow pages which are descendants of the current page. Any task which the
viewing user can perform an action on will be starred.
To Do
1.
2.
3.
4.
5.
More Trigger types.
More Action types.
Easy editing of WorkflowTypes.
Workflow of parent can depend on states of children
Introduce concept of 'Assignments', where at one workflow step a particular user is assigned to a role which nominates them to
perform other operations.
6. Think about the visual style – the current style is good for when workflow is 'out of band', that is, it's an activity undertaken by site
maintainers invisible to site users, but doesn't suit a 'Confluence as web-app' application, where workflow should blend in...
Approval Workflow
This page describes the details of an approval workflow.
Users may be members of an 'author' group which is allowed to edit pages, an 'approver' group which is allowed to approve edited
pages, or both groups (in which case they can't approve their own changes) or neither (in which case they are just consumers of the
content).
When an 'author' edits a page, the page goes into a 'editing in progress' state.
When an author views an 'editing in progress' page, they are presented with an option to submit the page for review. This puts the
page into the 'waiting for approval' state.
Members of the approver group have access to a page in confluence which automatically lists the pages waiting for approval.
When an 'approver' visits a 'waiting for approval' page, they are presented with options to accept or reject the changes. If they accept
the changes, the page goes to the 'accepted' state, where pages spend most of their life, otherwise it goes to the 'rejected' state.
Members of the 'author' group have access to a page in Confluence where they can see all the pages which they edited which have
been rejected, or are waiting for approval. They don't see pages other authors have edited.
When an author visits a page in the 'rejected' or 'waiting for approval' state, they have the option of withdrawing the change, which
moves the page to the accepted state, and rolls back to the most recent approved version.
When an author edits a page in the rejected state, it moves to the 'editing in progress' state.
All of this can be done with the DOC:Workflow Plugin Prototype.
But we probably also want to show consumers the most recently approved version of a page, not the one currently under review. Without
core Confluence changes, the best we can do is show users a banner which says "This content is being reviewed. The most recent approved
content is here".
XWork-WebWork Module
Available:
Confluence 1.4 and later
XWork plugin modules enable you to deploy XWork / WebWork actions and views as a part of your plugins.
For more information about plugins in general, read Confluence Plugin Guide.
To learn how to install and configure plugins (including macros), read Installing a Plugin.
For an introduction to writing your own plugins, read Writing Confluence Plugins.
On this page:
The XWork Plugin Module
Writing an Action
Accessing Your Actions
Creating a Velocity Template for Output
Notes
Important Security Note
Example
See also
The XWork Plugin Module
Each XWork module is deployed as a plugin module of type xwork and contains one of more XWork package elements.
Here is an example atlassian-plugin.xml file containing a single XWork module:
<atlassian-plugin name='List Search Macros' key='confluence.extra.livesearch'>
...
<xwork name="livesearchaction" key="livesearchaction">
<package name="livesearch" extends="default" namespace="/plugins/livesearch">
<default-interceptor-ref name="defaultStack" />
<action name="livesearch"
class="com.atlassian.confluence.extra.livesearch.LiveSearchAction">
<result name="success" type="velocity">
/templates/extra/livesearch/livesearchaction.vm
</result>
</action>
</package>
</xwork>
</atlassian-plugin>
the xwork element has no class attribute.
beneath this element, multiple package elements can be specified. These are standard XWork package elements, just as you would
specify in xwork.xml.
Writing an Action
For information on how to write a WebWork action, please consult the WebWork documentation.
WebWork actions must implement com.opensymphony.xwork.Action. However, we recommend you make your action extend
ConfluenceActionSupport, which provides a number of helper methods and components that are useful when writing an Action that works
within Confluence.
Other action base-classes can be found within Confluence, but we recommend you don't use them - the hierarchy of action classes in
Confluence is over-complicated, and likely to be simplified in the future in a way that will break your plugins.
Accessing Your Actions
Actions are added to the XWork core configuration within Confluence, which means they are accessed like any other action!
For example, given the above atlassian-plugin.xml, the livesearch action would be accessed at
http://yourserver/confluence/plugins/livesearch/livesearch.action.
Creating a Velocity Template for Output
Your Velocity template must be specified with a leading slash (/) in the plugin XML configuration, and the path must correspond to the path
inside the plugin JAR file.
In the above example, the Velocity template must be found in /templates/extra/livesearch/livesearchaction.vm inside the
plugin JAR file. In the example plugin's source code, this would mean the Velocity template should be created in
src/main/resources/template/extra/livesearch/.
Inside your Velocity template, you can use $action to refer to your action, and many other variables to access Confluence functionality. See
Confluence Objects Accessible From Velocity for more information.
Notes
Some issues to be aware of when developing or configuring an XWork plugin:
Your packages should always extend the default Confluence package. It is useful to be aware of what this provides to you in the
way of interceptors and result types. Extending any other package will modify that package's configuration across the entire
application, which is not supported or desirable.
You can give your packages any namespace you like, but we recommend using /plugins/unique/value - that is prefixing
plugin packages with /plugins and then adding a string globally unique to your plugin. The only name you can't use is servlet
as the /plugins/servlet URL pattern is reserved for Servlet Module.
Views must be bundled in the JAR file in order to be used by your actions. This almost always means using Velocity views.
It is useful to be aware of the actions and features already bundled with Confluence, for example your actions will all be auto-wired
by Spring (see Accessing Confluence Components from Plugin Modules) and your actions can use useful interfaces like PageAware
and SpaceAware to reduce the amount of work they need to do.
Currently only WebWork Modules are protected by the temporary secure administrator sessions. Other plugin types, such as REST
services or servlets are not checked for an administrator session.
All webwork modules mounted under /admin will automatically be protected by secure administrator sessions. To opt out of this
protection you can mark your class or webwork action method with the WebSudoNotRequired annotation. Conversely, all webwork
actions mounted outside the /admin namespace are not protected and can be opted in by adding the WebSudoRequired
annotation.
Both of these annotations work on the class or the action method. If you mark a method with the annotation, only action invocations
invoking that method will be affected by the annotations. If you annotate the class, any invocation to that class will be affected.
Sub-classes inherit these annotations.
Important Security Note
If you are writing an XWork plugin, it is very important that you read this security information: XWork Plugin Complex Parameters and
Security
Example
The LiveSearch example is a neat example of an Ajax-style Confluence plugin which uses a bundled XWork module to do it's work:
Find this example in the /plugins/macros/livesearch directory within your Confluence distribution.
See also
Accessing Confluence Components from Plugin Modules
Confluence Objects Accessible From Velocity
Velocity Context Module
XWork Plugin Complex Parameters and Security
Writing Confluence Plugins
XWork Plugin Complex Parameters and Security
This document describes changes that were made to the handling of XWork plugins in Confluence between versions 2.9
and 2.10. All developers writing XWork plugins for Confluence 2.10 and later should take note of this.
Complex XWork Parameters
XWork allows the setting of complex parameters on an XWork action object. For example, a URL parameter of formData.name=Charles
will be translated by XWork into the method calls getFormData().setName("Charles") by the XWork parameters interceptor. If
getFormData() returns null, XWork will attempt to create a new object of the appropriate return type using its default constructor, and then
set it with setFormData(newObject).
This leads to the potential for serious security vulnerabilities in XWork actions, as you can effectively call arbitrary methods on an Action
object. This led to the Parameter Injection issues in Confluence Security Advisory 2008-10-14. In Confluence 2.9 this issue was worked
around by filtering out all properties that were known to be dangerous, but for 2.10 a more complete solution that also protects against future
vulnerabilities has been introduced.
Because this vulnerability (and its solution) can affect plugins, plugin authors must now take extra steps to support complex form parameters.
The @ParameterSafe Annotation
From Confluence 2.10 and onwards, complex parameters are not permitted unless they are accompanied by a Java-level annotation
declaring that the parameter is "safe" for XWork to access. There are two ways to apply the annotation:
If a getter method is annotated with the @com.atlassian.xwork.ParameterSafe annotation, that method is accessable as a
complex parameter
If a class is annotated with the @com.atlassian.xwork.ParameterSafe annotation, any complex parameter that is of that type
is accessible
Only the initial method on the XWork action, or initial return value from the action class needs to be annotated, nested complex parameters
do not need further annotation.
So in the example above, to make the formData parameter you would do one of the following:
@ParameterSafe
public FormData getFormData() {
return formData;
}
or:
@ParameterSafe
public class FormData {
...
}
Be Careful
By placing the @ParameterSafe annotation on a method or class, you the developer are declaring that you have carefully inspected that
code for potential vulnerabilities. Things to be careful of:
DO NOT return live Hibernate persistent objects, as users may change values on them directly with parameters, and then those
changes will be saved to the database automatically
DO NOT return objects that contain setter methods that are used for anything but setting form parameter values, as those values will
be reachable by URL parameter injection
DO NOT return objects that have Spring-managed beans, live components, or hibernate objects accessible through getter methods,
as they will be accessible to URL parameter injection
Your safest bet is that if you are using an object to store complex parameters, make it a dumb: just setters that store state in the object itself
and no further behaviour. Any more functionality than that is dangerous.
Confluence User Macro Guide
User macros allow you to create simple formatting macros using the Confluence web interface.
To create a user macro:
Go to the Confluence Administration Console.
Select 'User Macros'.
Enter the macro metadata and input data as prompted. See the administrator's guide to writing user macros.
User Macro Plugins and Macro Plugins
If you want to distribute your user macro as a plugin, please see the developer's guide to the User Macro plugin module. If
you want to create more complex, programmatic macros in Confluence, you may need to write a Macro plugin module.
Note also that Macro modules and User Macro modules can appear in the Confluence Notation Guide, whereas user
macros do not. Here is an example of the Confluence Notation Guide.
Confluence Remote APIs
Confluence REST APIs - Prototype Only
Confluence XML-RPC and SOAP APIs
Remote API Specification for PDF Export
REV400 Confluence XML-RPC and SOAP APIs
Confluence REST APIs - Prototype Only
The Confluence REST APIs are a prototype only
Confluence’s REST APIs are evolving. Their functionality is currently limited to a subset of the existing Confluence API. We
plan to improve the REST APIs in future releases. Please expect some API changes. If you decide to experiment with
these APIs, we would welcome feedback – you can create an improvement request in the REST API component of our
JIRA project.
For production-ready API development, please refer to the XML-RPC and SOAP APIs. You may also find the JSON-RPC
plugin useful, which allows those APIs to be more easily accessed from web technologies such as Javascript/AJAX, or
simple HTTP requests.
The REST APIs are for developers who want to integrate Confluence into their application and for administrators who want to script
interactions with the Confluence server.
Introduction to Confluence's REST APIs
Confluence's REST APIs provide access to resources (data entities) via URI paths. To use a REST API, your application will make an HTTP
request and parse the response. By default, the response format is XML. If you wish, you can request JSON instead of XML. Your methods
will be the standard HTTP methods like GET, PUT, POST and DELETE.
Because the REST API is based on open standards, you can use any web development language to access the API.
A typical use case would be to search Confluence for a page or pages that match a given search term, then retrieve the content of the
page(s).
The Confluence REST API is currently read only. You cannot yet use it to update information in Confluence.
Confluence's REST APIs allow you to retrieve the following information:
A list of spaces, including high-level information about each space.
Detailed information about a space.
Search results using the Confluence search with various parameters.
The content of pages, blogs and comments.
A list of attachments for a given page or blog post.
A given attachment, specified by attachment ID.
Information about the user's session.
Translated UI text (message bundles) for a given internationalisation key.
Getting Started
If you would like to know more about REST in general, start with the RESTwiki's guide to REST In Plain English.
Then jump right in and try our REST resources:
Read our guide to using the REST APIs.
Find the REST resources you need in our REST resources reference guide.
Advanced Topics
Below are some links to in-depth information on developing REST APIs and plugins:
Developing your own REST APIs for Confluence: Confluence uses the Atlassian REST API to implement the Confluence APIs.
The REST plugin is bundled with Confluence. You can add your own REST APIs to Confluence by creating a Confluence plugin that
includes the REST plugin module.
Understanding the principles behind the Atlassian REST API design: You may be interested in the guidelines followed by the
Atlassian developers who are designing REST APIs for Atlassian applications, including the Confluence REST APIs.
RELATED TOPICS
Confluence Developer Documentation
Using the REST APIs - Prototype Only
This page contains information on the factors common across all or most of the Confluence REST APIs. For details of the specific REST
resources, please refer to the REST resources reference guide.
The Confluence REST APIs are a prototype only
Confluence’s REST APIs are evolving. Their functionality is currently limited to a subset of the existing Confluence API. We
plan to improve the REST APIs in future releases. Please expect some API changes. If you decide to experiment with
these APIs, we would welcome feedback – you can create an improvement request in the REST API component of our
JIRA project.
For production-ready API development, please refer to the XML-RPC and SOAP APIs. You may also find the JSON-RPC
plugin useful, which allows those APIs to be more easily accessed from web technologies such as Javascript/AJAX, or
simple HTTP requests.
On this page:
REST Authentication
REST Resources and URI Structure
Media Types
API Versions
HTTP Response Codes
Methods
REST Authentication
You can authenticate yourself for the REST APIs in two ways:
Log in to Confluence manually. You will then be authenticated for the REST APIs for that same browser session.
Use HTTP basic authentication (Authorisation HTTP header) containing 'Basic username:password'. Please note however,
username:password must be base64 encoded. The URL must also contain the 'os_authType=basic' query parameter.
REST Resources and URI Structure
URIs for a Confluence REST API resource have the following structure:
With context:
Or without context:
In Confluence 3.1 and Confluence 3.2, the only available api-name is prototype.
Examples:
With context:
Or without context:
Here is an explanation for each part of the URI:
host and port define the host and port where the Confluence application lives.
context is the servlet context of the Confluence installation. For example, the context might be confluence. Omit this section if
your URI does not include a context.
rest denotes the REST API.
api-name identifies a specific Confluence API. For example, admin is the API that allows interaction with the Confluence
Administration Console. (This is the path declared in the REST module type in the REST plugin descriptor.)
api-version is the API version number, e.g. 1 or 2. See the section on API version control.
resource-name identifies the required resource. In some cases, this may be a generic resource name such as /foo. In other
cases, this may include a generic resource name and key. For example, /foo returns a list of the foo items and /foo/{key}
returns the full content of the foo identified by the given key.
Refer to the details of the specific REST resources in the REST resources reference guide.
Media Types
The Confluence REST APIs return HTTP responses in one of the following formats:
Response Format
Requested via...
JSON
Requested via one of the following:
application/json in the HTTP Accept header
.json extension
XML
Requested via one of the following:
application/xml in the HTTP Accept header
.xml extension
API Versions
The Confluence REST APIs are subject to version control. The version number of an API appears in its URI. For example, use this URI
structure to request version 1 of the 'admin' API:
http://host:port/context/rest/prototype/1/...
To get the latest version of the API, you can also use the latest key-word. For example, if versions 1 and 2 of the 'admin' API are available,
the following two URIs will point to the same resources:
http://host:port/context/rest/prototype/latest/...
http://host:port/context/rest/prototype/2/...
Notes:
The API version number is an integer, such as 1 or 2.
The API version is independent of the Confluence release number.
The API version may, or may not, change with a new Confluence release. The API version number will change only when the
updates to the API break the API contract, requiring changes in the code which uses the API. An addition to the API does not
necessarily require a change to the API version number.
In the future, when there are multiple API versions available, it is the intention that each version of Confluence will support at least
two API versions i.e. the latest API version and the previous API version.
HTTP Response Codes
An error condition will return an HTTP error code as described in the Atlassian REST Guidelines.
Methods
You will use the standard HTTP methods to access Confluence via the REST APIs. Please refer to the REST resources reference guide to
see the HTTP methods available for each resource.
RELATED TOPICS
REST resources reference guide
Confluence REST APIs - Prototype Only
Confluence Developer Documentation
Confluence XML-RPC and SOAP APIs
On this page:
Introduction
XML-RPC Information
SOAP Information
Remote Methods
Authentication Methods
Administration
General
Spaces
Pages
Attachments
Blog Entries
Notifications
Search
Security
User Management
Labels
Data Objects
ServerInfo
SpaceSummary
Space
PageSummary
Page
PageUpdateOptions
PageHistorySummary
BlogEntrySummary
BlogEntry
RSS Feed
Search Result
Attachment
Comment
User
ContentPermission
ContentPermissionSet
Label
UserInformation
ClusterInformation
NodeStatus
ContentSummaries
ContentSummary
Script Examples
Changelog
Introduction
Confluence provides remote APIs as both XML-RPC and SOAP. This document refers to the XML-RPC specification. See SOAP details
below. XML-RPC and SOAP are both remote choices, as they have bindings for almost every language, making them very portable.
Which should I use?
SOAP is generally more useful from a strongly typed language (like Java or C#) but these require more setup.
XML-RPC is easier to use from a scripting language (like Perl, Python, AppleScript etc) and hence is often quicker to use.
XML-RPC Information
Some borrowed from the (VPWiki specification):
The URL for XML-RPC requests is http://<<confluence-install>>/rpc/xmlrpc .
All XML-RPC methods must be prefixed by confluence1. - to indicate this is version 1 of the API. We might introduce another
version in the future. For example to call the getPage method, the method name is confluence1.getPage .
All keys in structs are case sensitive.
All strings are decoded according to standard XML document encoding rules. Due to a bug in Confluence versions prior to 2.8,
strings sent via XML-RPC are decoded using the JVM platform default encoding (CONF-10213) instead of the XML encoding.
Confluence uses 64 bit long values for things like object IDs, but XML-RPC's largest supported numeric type is int32. As such, all
IDs and other long values must be converted to Strings when passed through XML-RPC API.
Anywhere you see the word Vector, you can interchange it with "Array" or "List" depending on what language you prefer. This is the
array data type as defined in the XML-RPC spec.
Anywhere you see the word Hashtable, you can interchange it with "Struct" or "Dictionary" or "Map" depending on what language
you prefer. This is the struct data type as defined in the XML-RPC spec.
The default session lifetime is 60 minutes, but that can be controlled by the deployer from the web.xml file. This can be found in the
/confluence/WEB-INF/ folder.
SOAP Information
The SOAP API follows the same methods as below, except with typed objects (as SOAP allows for).
To find out more about the SOAP API, simply point your SOAP 'stub generator' at the WSDL file, located at
http://<confluence-install>/rpc/soap-axis/confluenceservice-v1?wsdl .
For reference, the confluence.atlassian.com WSDL file is here.
The Confluence Command Line Interface is a good place to get a functioning client.
Remote Methods
Authentication Methods
String login(String username, String password) - log in a user. Returns a String authentication token to be passed as authentication
to all other remote calls. It's not bulletproof auth, but it will do for now. Must be called before any other method in a 'remote
conversation'. From 1.3 onwards, you can supply an empty string as the token to be treated as being the anonymous user.
boolean logout(String token) - remove this token from the list of logged in tokens. Returns true if the user was logged out, false if
they were not logged in in the first place.
Administration
String exportSite(String token, boolean exportAttachments) - exports a Confluence instance and returns a String holding the URL for
the download. The boolean argument indicates whether or not attachments ought to be included in the export.
ClusterInformation getClusterInformation(String token) - returns information about the cluster this node is part of.
Vector getClusterNodeStatuses(String token) - returns a Vector of NodeStatus objects containing information about each node in the
cluster.
boolean isPluginEnabled(String token, String pluginKey) - returns true if the plugin is installed and enabled, otherwise false.
boolean installPlugin(String token, String pluginFileName, byte[] pluginData) - installs a plugin in Confluence. Returns false if the file
is not a JAR or XML file. Throws an exception if the installation fails for another reason.
General
ServerInfo getServerInfo(String token) - retrieve some basic information about the server being connected to. Useful for clients that
need to turn certain features on or off depending on the version of the server. (Since 1.0.3)
Spaces
Retrieval
Vector<SpaceSummary> getSpaces(String token) - returns all the SpaceSummaries that the current user can see.
Space getSpace(String token, String spaceKey) - returns a single Space. If the spaceKey does not exist: earlier versions of
Confluence will throw an Exception. Later versions (3.0+) will return a null object.
String exportSpace(String token, String spaceKey, String exportType) - exports a space and returns a String holding the URL for the
download. The export type argument indicates whether or not to export in XML or HTML format - use "TYPE_XML" or
"TYPE_HTML" respectively. Also, using "all" will select TYPE_XML.
In Confluence 3.0, the remote API specification for PDF exports changed. You can no longer use this 'exportSpace' method to export a
space to PDF. Please refer to Remote API Specification for PDF Export for current remote API details on this feature.
Management
Space addSpace(String token, Space space) - create a new space, passing in name, key and description.
Boolean removeSpace(String token, String spaceKey) - remove a space completely.
Space addPersonalSpace(String token, Space personalSpace, String userName) - add a new space as a personal space.
boolean convertToPersonalSpace(String token, String userName, String spaceKey, String newSpaceName, boolean updateLinks) convert an existing space to a personal space.
Space storeSpace(String token, Space space) - create a new space if passing in a name, key and description or update the
properties of an existing space. Only name, homepage or space group can be changed.
boolean importSpace(String token, byte[] zippedImportData) - import a space into Confluence. Note that this uses a lot of memory
- about 4 times the size of the upload. The data provided should be a zipped XML backup, the same as exported by Confluence.
ContentSummaries getTrashContents(String token, String spaceKey, int offset, int count) - get the contents of the trash for the given
space, starting at 'offset' and returning at most 'count' items.
boolean purgeFromTrash(String token, String spaceKey, long contentId) - remove some content from the trash in the given space,
deleting it permanently.
boolean emptyTrash(String token, String spaceKey) - remove all content from the trash in the given space, deleting them
permanently.
Pages
Retrieval
Vector<PageSummary> getPages(String token, String spaceKey) - returns all the PageSummaries in the space. Doesn't include
pages which are in the Trash. Equivalent to calling Space.getCurrentPages().
Page getPage(String token, Long pageId) - returns a single Page
Page getPage(String token, String spaceKey, String pageTitle) - returns a single Page
Vector<PageHistorySummary> getPageHistory(String token, String pageId) - returns all the PageHistorySummaries - useful for
looking up the previous versions of a page, and who changed them.
Permissions
Vector<ContentPermissionSet> getContentPermissionSets(String token, String contentId) - returns all the page level permissions for
this page as ContentPermissionSets
Hashtable getContentPermissionSet(String token, String contentId, String permissionType) - returns the set of permissions on a
page as a map of type to a list of ContentPermission, for the type of permission which is either 'View' or 'Edit'
boolean setContentPermissions(String token, String contentId, String permissionType, Vector permissions) - sets the page-level
permissions for a particular permission type (either 'View' or 'Edit') to the provided vector of ContentPermissions. If an empty list of
permissions are passed, all page permissions for the given type are removed. If the existing list of permissions are passed, this
method does nothing.
Dependencies
Vector<Attachment> getAttachments(String token, String pageId) - returns all the Attachments for this page (useful to point users to
download them with the full file download URL returned).
Vector<PageSummary> getAncestors(String token, String pageId) - returns all the ancestors (as PageSummaries) of this page
(parent, parent's parent etc).
Vector<PageSummary> getChildren(String token, String pageId) - returns all the direct children (as PageSummaries) of this page.
Vector<PageSummary> getDescendents(String token, String pageId) - returns all the descendents (as PageSummaries) of this page
(children, children's children etc).
Vector<Comment> getComments(String token, String pageId) - returns all the comments for this page.
Comment getComment(String token, String commentId) - returns an individual comment.
Comment addComment(String token, Comment comment) - adds a comment to the page.
boolean removeComment(String token, String commentId) - removes a comment from the page.
Management
Page storePage(String token, Page page) - adds or updates a page. For adding, the Page given as an argument should have space,
title and content fields at a minimum. For updating, the Page given should have id, space, title, content and version fields at a
minimum. The parentId field is always optional. All other fields will be ignored. Note: the return value can be null, if an error that did
not throw an exception occurred. Operates exactly like updatePage() if the page already exists.
Page updatePage(String token, Page page, PageUpdateOptions pageUpdateOptions) - updates a page. The Page given should
have id, space, title, content and version fields at a minimum. The parentId field is always optional. All other fields will be ignored.
Note: the return value can be null, if an error that did not throw an exception occurred.
String renderContent(String token, String spaceKey, String pageId, String content) - returns the HTML rendered content for this
page. If 'content' is provided, then that is rendered as if it were the body of the page (useful for a 'preview page' function). If it's not
provided, then the existing content of the page is used instead (ie useful for 'view page' function).
String renderContent(String token, String spaceKey, String pageId, String content, Hashtable parameters) - Like the above
renderContent(), but you can supply an optional hash (map, dictionary, etc) containing additional instructions for the renderer.
Currently, only one such parameter is supported:
"style = clean" Setting the "style" parameter to "clean" will cause the page to be rendered as just a single block of HTML
within a div, without the HTML preamble and stylesheet that would otherwise be added.
void removePage(String token, String pageId) - removes a page
void movePage(String token, String sourcePageId, String targetPageId, String position) - moves a page's position in the hierarchy.
(since 2.8)
sourcePageId - the id of the page to be moved.
targetPageId - the id of the page that is relative to the sourcePageId page being moved.
position - "above", "below", or "append". (Note that the terms 'above' and 'below' refer to the relative vertical position of the
pages in the page tree.)
void movePageToTopLevel(String token, String pageId, String targetSpaceKey) - moves a page to the top level of the target space.
This corresponds to PageManager - movePageToTopLevel. (since 2.8)
Position Keys for Moving a Page
Position Key
Effect
above
source and target become/remain sibling pages and the source is moved above the target in the page tree.
below
source and target become/remain sibling pages and the source is moved below the target in the page tree.
append
source becomes a child of the target
Attachments
Retrieval
Attachment getAttachment(String token, String pageId, String fileName, String versionNumber) - get information about an
attachment.
byte[] getAttachmentData(String token, String pageId, String fileName, String versionNumber) - get the contents of an attachment.
To retrieve information or content from the current version of an attachment, use a 'versionNumber' of "0".
Management
Attachment addAttachment(String token, long contentId, Attachment attachment, byte[] attachmentData) - add a new attachment to
a content entity object. Note that this uses a lot of memory - about 4 times the size of the attachment. The 'long contentId' is
actually a String pageId for XML-RPC.
boolean removeAttachment(String token, String contentId, String fileName) - remove an attachment from a content entity object.
boolean moveAttachment(String token, String originalContentId, String originalName, String newContentEntityId, String newName) move an attachment to a different content entity object and/or give it a new name.
Blog Entries
Vector<BlogEntrySummary> getBlogEntries(String token, String spaceKey) - returns all the BlogEntrySummaries in the space.
BlogEntry getBlogEntry(String token, String pageId) - returns a single BlogEntry.
BlogEntry storeBlogEntry(String token, BlogEntry entry) - add or update a blog entry. For adding, the BlogEntry given as an
argument should have space, title and content fields at a minimum. For updating, the BlogEntry given should have id, space, title,
content and version fields at a minimum. All other fields will be ignored.
BlogEntry getBlogEntryByDayAndTitle(String token, String spaceKey, int dayOfMonth, String postTitle) - Retrieves a blog post by
specifying the day it was published in the current month, its title and its space key.
BlogEntry getBlogEntryByDateAndTitle(String token, String spaceKey, int year, int month, int dayOfMonth, String postTitle) - retrieve
a blog post by specifying the date it was published, its title and its space key.
Notifications
The notification methods in the remote API are available in Confluence 3.5 and later.
boolean watchPage(String token, long pageId) - watch a page or blog post as the current user, returns false if a space, page or blog
is already being watched
boolean watchSpace(String token, String spaceKey) - watch a space as the current user, returns false if the space is already
watched
boolean watchPageForUser(String token, long pageId, String username) - add a watch on behalf of another user (space
administrators only)
boolean isWatchingPage(String token, long pageId, String username) - check whether a user is watching a page (space
administrators only, if the username isn't the current user)
boolean isWatchingSpace(String token, String spaceKey, String username) - check whether a user is watching a space (space
administrators only, if the username isn't the current user)
Vector<User> getWatchersForPage(String token, long pageId) - return the watchers for the page (space administrators only)
Vector<User> getWatchersForSpace(String token, String spaceKey) - return the watchers for the space (space administrators only).
Search
Vector<SearchResult> search(String token, String query, int maxResults) - return a list of SearchResults which match a given
search query (including pages and other content types). This is the same as a performing a parameterised search (see below) with
an empty parameter map.
Vector<SearchResult> search(String token, String query, Map parameters, int maxResults) - (since 1.3) like the previous search,
but you can optionally limit your search by adding parameters to the parameter map. If you do not include a parameter, the default is
used instead.
Parameters for Limiting Search Results
key
description
values
default
spaceKey
search a single space
(any valid space
key)
Search all
spaces
type
Limit the content types of the items to be returned in the search results.
page
blogpost
mail
comment
attachment
spacedescription
personalinformation
Search all types
modified
Search recently modified content
TODAY
YESTERDAY
LASTWEEK
LASTMONTH
No limit
contributor
The original creator or any editor of Confluence content. For mail, this is the person
who imported the mail, not the person who sent the email message.
Username of a
Confluence user.
Results are not
filtered by
contributor
Security
Vector<String> getPermissions(String token, String spaceKey) - Returns a Vector of Strings representing the permissions the current
user has for this space (a list of "view", "modify", "comment" and / or "admin").
Vector<String> getPermissionsForUser(String token, String spaceKey, String userName) - Returns a Vector of Strings representing
the permissions the given user has for this space. (since 2.1.4)
Vector<Permission> getPagePermissions(String token, String pageId) - Returns a Vector of Permissions representing the
permissions set on the given page.
Vector<String> getSpaceLevelPermissions(String token) - returns all of the space level permissions which may be granted. This is a
list of possible permissions to use with addPermissionToSpace, below, not a list of current permissions on a Space.
boolean addPermissionToSpace(String token, String permission, String remoteEntityName, String spaceKey) - Give the entity
named remoteEntityName (either a group or a user) the permission permission on the space with the key spaceKey.
boolean addPermissionsToSpace(String token, Vector permissions, String remoteEntityName, String spaceKey) - Give the entity
named remoteEntityName (either a group or a user) the permissions permissions on the space with the key spaceKey.
boolean removePermissionFromSpace(String token, String permission, String remoteEntityName, String spaceKey) - Remove the
permission permission} from the entity named {{remoteEntityName (either a group or a user) on the space with the
key spaceKey.
boolean addAnonymousPermissionToSpace(String token, String permission, String spaceKey) - Give anonymous users the
permission permission on the space with the key spaceKey. (since 2.0)
boolean addAnonymousPermissionsToSpace(String token, Vector permissions, String spaceKey) - Give anonymous users the
permissions permissions on the space with the key spaceKey. (since 2.0)
boolean removeAnonymousPermissionFromSpace(String token, String permission,String spaceKey) - Remove the permission
permission} from anonymous users on the space with the key {{spaceKey. (since 2.0)
boolean removeAllPermissionsForGroup(String token, String groupname) - Remove all the global and space level permissions for
groupname.
Space permissions
Names are as shown in Space Admin > Permissions. Values can be passed to security remote API methods above which take a space
permission parameter.
Permission name
String value
Description
View
VIEWSPACE
View all content in the space
Pages - Create
EDITSPACE
Create new pages and edit existing ones
Pages - Export
EXPORTPAGE
Export pages to PDF, Word
Pages - Restrict
SETPAGEPERMISSIONS
Set page-level permissions
Pages - Remove
REMOVEPAGE
Remove pages
News - Create
EDITBLOG
Create news items and edit existing ones
News - Remove
REMOVEBLOG
Remove news
Comments - Create
COMMENT
Add comments to pages or news in the space
Comments - Remove
REMOVECOMMENT
Remove the user's own comments
Attachments - Create
CREATEATTACHMENT
Add attachments to pages and news
Attachments - Remove
REMOVEATTACHMENT
Remove attachments
Mail - Remove
REMOVEMAIL
Remove mail
Space - Export
EXPORTSPACE
Export space to HTML or XML
Space - Admin
SETSPACEPERMISSIONS
Administer the space
In Confluence 3.0, the remote API specification for PDF exports changed. Consequently, the 'Space - Export' permission above no
longer applies to PDF exports. Please refer to Remote API Specification for PDF Export for current remote API details on this feature.
User Management
User getUser(String token, String username) - get a single user
void addUser(String token, User user, String password) - add a new user with the given password
void addGroup(String token, String group) - add a new group
Vector<String> getUserGroups(String token, String username) - get a user's current groups
void addUserToGroup(String token, String username, String groupname) - add a user to a particular group
boolean removeUserFromGroup(String token, String username, String groupname) - remove a user from a group.
boolean removeUser(String token, String username) - delete a user.
boolean removeGroup(String token, String groupname, String defaultGroupName) - remove a group. If defaultGroupName is
specified, users belonging to groupname will be added to defaultGroupName.
Vector<String> getGroups(String token) - gets all groups
boolean hasUser(String token, String username) - checks if a user exists
boolean hasGroup(String token, String groupname) - checks if a group exists
boolean editUser(String token, RemoteUser remoteUser) - edits the details of a user
boolean deactivateUser(String token, String username) - deactivates the specified user
boolean reactivateUser(String token, String username) - reactivates the specified user
Vector<String> getActiveUsers(String token, boolean viewAll) - returns all registered users
boolean setUserInformation(String token, UserInformation userInfo) - updates user information
UserInformation getUserInformation(String token, String username) - Retrieves user information
boolean changeMyPassword(String token, String oldPass, String newPass) - changes the current user's password
boolean changeUserPassword(String token, String username, String newPass) - changes the specified user's password
boolean addProfilePicture(String token, String userName, String fileName, String mimeType, byte[] pictureData) - add and set the
profile picture for a user.
Labels
Vector getLabelsById(String token, long objectId) - Returns all Labels for the given ContentEntityObject ID
Vector getMostPopularLabels(String token, int maxCount) - Returns the most popular Labels for the Confluence instance, with a
specified maximum number.
Vector getMostPopularLabelsInSpace(String token, String spaceKey, int maxCount) - Returns the most popular Labels for the given
spaceKey, with a specified maximum number of results.
Vector getRecentlyUsedLabels(String token, int maxResults) - Returns the recently used Labels for the Confluence instance, with a
specified maximum number of results.
Vector getRecentlyUsedLabelsInSpace(String token, String spaceKey, int maxResults) - Returns the recently used Labels for the
given spaceKey, with a specified maximum number of results.
Vector getSpacesWithLabel(String token, String labelName) - Returns an array of Spaces that have been labelled with labelName.
Vector getRelatedLabels(String token, String labelName, int maxResults) - Returns the Labels related to the given label name, with a
specified maximum number of results.
Vector getRelatedLabelsInSpace(String token, String labelName, String spaceKey, int maxResults) - Returns the Labels related to
the given label name for the given spaceKey, with a specified maximum number of results.
Vector getLabelsByDetail(String token, String labelName, String namespace, String spaceKey, String owner) - Retrieves the Labels
matching the given labelName, namespace, spaceKey or owner.
Vector getLabelContentById(String token, long labelId) - Returns the content for a given label ID
Vector getLabelContentByName(String token, String labelName) - Returns the content for a given label name.
Vector getLabelContentByObject(String token, Label labelObject) - Returns the content for a given Label object.
Vector getSpacesContainingContentWithLabel(String token, String labelName) - Returns all Spaces that have content labelled with
labelName.
boolean addLabelByName(String token, String labelName, long objectId) - Adds label(s) to the object with the given
ContentEntityObject ID. For multiple labels, labelName should be in the form of a space-separated or comma-separated string.
boolean addLabelById(String token, long labelId, long objectId) - Adds a label with the given ID to the object with the given
ContentEntityObject ID.
boolean addLabelByObject(String token, Label labelObject, long objectId) - Adds the given label object to the object with the given
ContentEntityObject ID.
boolean addLabelByNameToSpace(String token, String labelName, String spaceKey) - Adds a label to the object with the given
ContentEntityObject ID.
boolean removeLabelByName(String token, String labelName, long objectId) - Removes the given label from the object with the
given ContentEntityObject ID.
boolean removeLabelById(String token, long labelId, long objectId) - Removes the label with the given ID from the object with the
given ContentEntityObject ID.
boolean removeLabelByObject(String token, Label labelObject, long objectId) - Removes the given label object from the object with
the given ContentEntityObject ID.
boolean removeLabelByNameFromSpace(String token, String labelName, String spaceKey) - Removes the given label from the
given spaceKey.
Data Objects
Most returned structs have a summary and a detailed form:
The summary form is a primary key (ie space key, page id) and a representative form (ie space name, page title)
The detailed form will have all of the entity details as might be needed for the client.
Unless otherwise specified, all returned structs are in detailed form.
ServerInfo
Key
Type
Value
majorVersion
int
the major version number of the Confluence instance
minorVersion
int
the minor version number of the Confluence instance
patchLevel
int
the patch-level of the Confluence instance
buildId
String
the build ID of the Confluence instance (usually a number)
developmentBuild
Boolean
Whether the build is a developer-only release or not
baseUrl
String
The base URL for the confluence instance
Note: Version 1.0.3 of Confluence would be major-version: 1, minor-version: 0, patch-level: 3. Version 2.0 would have a patch-level of 0,
even if it's not visible in the version number.
SpaceSummary
Key
Type
Value
key
String
the space key
name
String
the name of the space
type
String
type of the space
url
String
the url to view this space online
Space
Key
Type
Value
key
String
the space key
name
String
the name of the space
url
String
the url to view this space online
homePage
String
the id of the space homepage
description
String
the HTML rendered space description
PageSummary
Key
Type
Value
id
String
the id of the page
space
String
the key of the space that this page belongs to
parentId
String
the id of the parent page
title
String
the title of the page
url
String
the url to view this page online
locks
int
the number of locks current on this page
Page
Key
Type
Value
id
String
the id of the page
space
String
the key of the space that this page belongs to
parentId
String
the id of the parent page
title
String
the title of the page
url
String
the url to view this page online
version
int
the version number of this page
content
String
the page content
created
Date
timestamp page was created
creator
String
username of the creator
modified
Date
timestamp page was modified
modifier
String
username of the page's last modifier
homePage
Boolean
whether or not this page is the space's homepage
locks
int
the number of locks current on this page
contentStatus
String
status of the page (eg. current or deleted)
current
Boolean
whether the page is current and not deleted
PageUpdateOptions
Key
Type
Value
versionComment
String
Edit comment for the updated page
minorEdit
Boolean
Is this update a 'minor edit'? (default value: false)
PageHistorySummary
Key
Type
Value
id
String
the id of the historical page
version
int
the version of this historical page
modifier
String
the user who made this change
modified
Date
timestamp change was made
versionComment
String
the comment made when the version was changed
BlogEntrySummary
Key
Type
Value
id
String
the id of the blog entry
space
String
the key of the space that this blog entry belongs to
title
String
the title of the blog entry
url
String
the url to view this blog entry online
locks
int
the number of locks current on this page
publishDate
Date
the date the blog post was published
BlogEntry
Key
Type
Value
id
String
the id of the blog entry
space
String
the key of the space that this blog entry belongs to
title
String
the title of the page
url
String
the url to view this blog entry online
version
int
the version number of this blog entry
content
String
the blog entry content
locks
int
the number of locks current on this page
RSS Feed
Key
Type
Value
url
String
the URL of the RSS feed
title
String
the feed's title
Search Result
Key
Type
Value
title
String
the feed's title
url
String
the remote URL needed to view this search result online
excerpt
String
a short excerpt of this result if it makes sense
type
String
the type of this result - page, comment, spacedesc, attachment, userinfo, blogpost, status
id
String
the long ID of this result (if the type has one)
Attachment
Key
Type
Value
id
String
numeric id of the attachment
pageId
String
page ID of the attachment
title
String
title of the attachment
fileName
String
file name of the attachment (Required)
fileSize
String
numeric file size of the attachment in bytes
contentType
String
mime content type of the attachment (Required)
created
Date
creation date of the attachment
creator
String
creator of the attachment
url
String
url to download the attachment online
comment
String
comment for the attachment (Required)
Comment
Key
Type
Value
id
String
numeric id of the comment
pageId
String
page ID of the comment
parentId
String
parent ID of the comment
title
String
title of the comment
content
String
notated content of the comment (use renderContent to render)
url
String
url to view the comment online
created
Date
creation date of the attachment
creator
String
creator of the attachment
Key
Type
Value
name
String
the username of this user
User
fullname
String
the full name of this user
email
String
the email address of this user
url
String
the url to view this user online
ContentPermission
Key
Type
Value
type
String
The type of permission. One of 'View' or 'Edit'
userName
String
The username of the user who is permitted to see or edit the content. Null if this is a group permission.
groupName
String
The name of the group who is permitted to see or edit the content. Null if this is a user permission.
ContentPermissionSet
Key
Type
Value
type
String
The type of permission. One of 'View' or 'Edit'
contentPermissions
List
The permissions. Each item is a ContentPermission.
Label
Key
Type
Value
name
String
the nameof the label
owner
String
the username of the owner
namespace
String
the namespace of the label
id
String
the ID of the label
UserInformation
Key
Type
Value
username
String
the username of this user
content
String
the user description
creatorName
String
the creator of the user
lastModifierName
String
username of the user's last modifier
version
int
the version
id
String
the ID of the user
creationDate
Date
the date the user was created
lastModificationDate
Date
the date the user was last modified
ClusterInformation
Key
Type
Value
isRunning
boolean
true if this node is part of a cluster.
name
String
the name of the cluster.
memberCount
int
the number of nodes in the cluster, including this node (this will be zero if this node is not clustered.)
description
String
a description of the cluster.
multicastAddress
String
the address that this cluster uses for multicasr communication.
multicastPort
String
the port that this cluster uses for multicast communication.
NodeStatus
Key
Type
Value
nodeId
int
an integer uniquely identifying the node within the cluster.
jvmStats
Map
a Map containing attributes about the JVM memory usage of node. Keys are "total.memory", "free.memory",
"used.memory".
props
Map
a Map containing attributes of the node. Keys are "system.date", "system.time", "system.favourite.colour",
"java.version", "java.vendor",
"jvm.version", "jvm.vendor", "jvm.implemtation.version", "java.runtime", "java.vm", "user.name.word", "user.timezone",
"operating.system", "os.architecture", "fs.encoding".
buildStats
Map
a Map containing attributes of the build of Confluence running on the node. Keys are "confluence.home",
"system.uptime", "system.version",
"build.number".
ContentSummaries
Key
Type
Value
totalAvailable
int
The total number of content available to be retrieved.
offset
int
The index of the first content retrieved.
content
Vector of ContentSummary
list of the retrieved content.
ContentSummary
Key
Type
Value
id
String
The ID of the content.
type
String
The type of content (e.g. "page", "comment", "blog").
space
String
The key of the space to which the content belongs.
status
String
The current status of the content (e.g. "current", "deleted").
title
String
The title of the content.
created
Date
Timestamp page was created.
creator
String
Username of the creator.
modified
Date
Timestamp content was modified.
modifier
String
Username of content's last modifier.
Script Examples
The Confluence User Community space contains various examples of scripts.
Changelog
Confluence 3.5
Added notification methods: watchPage, watchSpace, watchPageForUser, getWatchersForPage, getWatchersForSpace
Added blog post retrieval method: getBlogEntryByDateAndTitle
Added space management methods: getTrashContents, purgeFromTrash, emptyTrash.
Added data objects: ContentSummaries, ContentSummary.
Confluence 2.10
Added updatePage.
Confluence 2.9
Search: Removed option 'all' in table of content types and changed the default to 'All'. If you need to search for all types, simply omit
the 'type' restriction.
Search: Added option 'contributor' to the table of filter options.
Confluence 2.3
Added getClusterInformation and getClusterNodeStatuses.
Confluence 2.2
Added addPersonalSpace, convertToPersonalSpace and addProfilePicture.
Confluence 2.1.4
Added getPermissionsForUser.
Confluence 2.0
Updated getLocks() to getPagePermissions()
Added addAttachment, getAttachment, getAttachmentData, removeAttachment and moveAttachment methods to allow
remote attachment handling. Note that adding large attachments with this API uses a lot of memory during the addAttachment
operation.
Added addAnonymousPermissionToSpace, addAnonymousPermissionsToSpace and
removeAnonymousPermissionFromSpace.
Added the addComment and removeComment methods for comment manipulation.
Added hasGroup and hasUser methods to determine if a group or user exists.
Added editUser method.
Added ability to deactivate and reactivate users.
Added getActiveUsers method to retrieve a user list.
Added ability to change the user password.
Added ability to retrieve and modify user information.
Added ability to retrieve, add and remove labels.
Added getBlogEntryByDayAndTitle
Confluence 1.4
Added new exportSpace and exportSite methods to build exports of an individual space or an entire Confluence instance and
return with a URL leading to the download.
Added new getChildren and getDescendents methods to get the direct children and all descendents of a given page.
Added new getAncestors method to get the ancestors of a given page.
Removed the old getLocks as locks are superceded by page level permissions.
Added new getPagePermissions method to retrieve page level permissions.
Added new removeUser, removeGroup, removeAllPermissionsForGroup, addUserToGroup and removeUserFromGroup
methods.
Added new addPermissionToSpace method.
Added new Permission data object.
Added new getSpaceLevelPermissions method.
Confluence 1.3
Added new getPage method which retrieves a page by space key and page title.
Added new removeSpace method to remove an entire space.
Added ability to limit search by parameters.
Allow anonymous access.
Confluence 1.2
renderContent takes an optional hashtable for rendering hints, the only one supported right now is "style=clean"
Confluence 1.1
getLocks gives you back a list of any locks that apply to a given page
added a locks field to the various Page structs containing a count of any applicable page-level locks
CRUD methods added for blog-posts
Confluence 1.0.3
getServerInfo gives you some basic information about the server version CONF1123
storePage now allows you to change the page's name (incoming links are all renamed) CONF-974
storePage now reliably allows you to re-parent pages
WSDL now respects the server's configured base URL, allowing it to work on proxy-hosted servers CONF-1088
RELATED TOPICS
Confluence Plugin Guide
Confluence User Macro Guide
Confluence Remote APIs
Development Resources
Remote API Specification for PDF Export
Available:
Confluence 3.0 and later
In Confluence 3.0, we moved the PDF export engine to a Confluence plugin. Therefore, you can no longer use Confluence's built-in remote
API to export a space as a PDF. There is a new replacement API that is part of the new PDF export Confluence plugin.
On this page:
XML-RPC Information
SOAP Information
Methods
XML-RPC Information
The URL for XML-RPC requests is http://<<confluence-install>>/rpc/xmlrpc .
All XML-RPC methods must be prefixed by pdfexport.
SOAP Information
To find out more about the SOAP API, simply point your SOAP 'stub generator' at the WSDL file, located at
http://<confluence-install>/rpc/soap-axis/pdfexport?wsdll.
For reference, the pdfexport WSDL file is here.
Methods
String login(String username, String password) - log in a user. Returns a String authentication token to be passed as authentication
to all other remote calls. It's not bulletproof auth, but it will do for now. Must be called before any other method in a 'remote
conversation'. From 1.3 onwards, you can supply an empty string as the token to be treated as being the anonymous user.
public String exportSpace(String token, String spaceKey) - exports the entire space as a PDF. Returns a url to download the
exported PDF. Depending on how you have Confluence set up, this URL may require you to authenticate with Confluence. Note that
this will be normal Confluence authentication, not the token based authentication that the web service uses.
REV400 Confluence XML-RPC and SOAP APIs
This page is a draft in progress and visible to atlassian-staff only.
On this page:
Introduction
XML-RPC Information
SOAP Information
Remote API version 1 and version 2
Remote Methods
Authentication Methods
Administration
General
Spaces
Pages
Attachments
Blog Entries
Notifications
Search
Security
User Management
Labels
Data Objects
ServerInfo
SpaceSummary
Space
PageSummary
Page
PageUpdateOptions
PageHistorySummary
BlogEntrySummary
BlogEntry
RSS Feed
Search Result
Attachment
Comment
User
ContentPermission
ContentPermissionSet
Label
UserInformation
ClusterInformation
NodeStatus
ContentSummaries
ContentSummary
Script Examples
Changelog
Introduction
Confluence provides remote APIs as both XML-RPC and SOAP. This document refers to the XML-RPC specification. See SOAP details
below\. XML-RPC and SOAP are both remote choices, as they have bindings for almost every language, making them very portable.
Which should I use?
SOAP is generally more useful from a strongly typed language (like Java or C#) but these require more setup.
XML-RPC is easier to use from a scripting language (like Perl, Python, AppleScript etc) and hence is often quicker to use.
XML-RPC Information
Some borrowed from the (VPWiki specification):
The URL for XML-RPC requests is http://<<confluence-install>>/rpc/xmlrpc .
All XML-RPC methods must be prefixed by confluence2. - to indicate this is version 2 of the API. For example to call the
getPage method, the method name is confluence2.getPage . There is also a version 1 API available see below.
All keys in structs are case sensitive.
All strings are decoded according to standard XML document encoding rules. Due to a bug in Confluence versions prior to 2.8,
strings sent via XML-RPC are decoded using the JVM platform default encoding (CONF-10213) instead of the XML encoding.
Confluence uses 64 big long values for things like object IDs, but XML-RPC's largest supported numeric type is int32. As such, all
IDs and other long values must be converted to Strings when passed through XML-RPC API.
Anywhere you see the word Vector, you can interchange it with "Array" or "List" depending on what language you prefer. This is the
array data type as defined in the XML-RPC spec.
Anywhere you see the word Hashtable, you can interchange it with "Struct" or "Dictionary" or "Map" depending on what language
you prefer. This is the struct data type as defined in the XML-RPC spec.
The default session lifetime is 60 minutes, but that can be controlled by the deployer from the web.xml file. This can be found in the
/confluence/WEB-INF/ folder.
SOAP Information
The SOAP API follows the same methods as below, except with typed objects (as SOAP allows for).
To find out more about the SOAP API, simply point your SOAP 'stub generator' at the WSDL file, located at
http://<confluence-install>/rpc/soap-axis/confluenceservice-v2?wsdl .
For reference, the confluence.atlassian.com WSDL file is here.
The Confluence Command Line Interface is a good place to get a functioning client.
Remote API version 1 and version 2
Confluence 4.0 introduced changes to the way page content is stored in Confluence. Page content is no longer stored as wiki markup, but is
now stored in an XHTML based storage format. Confluence 4.0 also introduced a new version 2 remote API. The new version 2 API
implements the same methods as the version 1 API, however all content is stored and retrieved using the storage format. This means that
you cannot, for example, create a page using wiki markup with the version 2 API, you must instead define the page using the storage format.
Atlassian recommends that you migrate towards this new API.
The version 1 remote API will continue to work with Confluence 4.0. You will be able to create pages, blogs and comments in wiki markup
using the version 1 API. However you will no longer be able to retrive pages, blogs and comments using the version 1 API. Specifically the
following methods will no longer work with the version 1 API:
getBlogEntryByDayAndTitle(String token, String spaceKey, int dayOfMonth, String postTitle)
getBlogEntryByDateAndTitle(String token, String spaceKey, int year, int month, int dayOfMonth, String postTitle)
getBlogEntry(String token, long entryId)
getPage(String token, String spaceKey, String pageTitle)
getPage(String token, long pageId)
getComments(String token, long pageId)
getComment(String token, long commentId)
You can use both APIs in a client, creating content in wikmarkup using the version 1 API and retrieving it in the storage format using the
version 2 API. This may be useful stepping stone for clients to move towards the version 2 API. You should note that there is no longer any
way to retrive content from Confluence as wiki markup.
To aid the migration from the version 1 API to the version 2 API a new method convertWikiToStorageFormat(String token, String markup)
has been added to both versions of the API.
Remote Methods
Authentication Methods
String login(String username, String password) - log in a user. Returns a String authentication token to be passed as authentication
to all other remote calls. It's not bulletproof auth, but it will do for now. Must be called before any other method in a 'remote
conversation'. From 1.3 onwards, you can supply an empty string as the token to be treated as being the anonymous user.
boolean logout(String token) - remove this token from the list of logged in tokens. Returns true if the user was logged out, false if
they were not logged in in the first place.
Administration
String exportSite(String token, boolean exportAttachments) - exports a Confluence instance and returns a String holding the URL for
the download. The boolean argument indicates whether or not attachments ought to be included in the export.
ClusterInformation getClusterInformation(String token) - returns information about the cluster this node is part of.
Vector getClusterNodeStatuses(String token) - returns a Vector of NodeStatus objects containing information about each node in the
cluster.
boolean isPluginEnabled(String token, String pluginKey) - returns true if the plugin is installed and enabled, otherwise false.
boolean installPlugin(String token, String pluginFileName, byte[] pluginData) - installs a plugin in Confluence. Returns false if the file
is not a JAR or XML file. Throws an exception if the installation fails for another reason.
General
ServerInfo getServerInfo(String token) - retrieve some basic information about the server being connected to. Useful for clients that
need to turn certain features on or off depending on the version of the server. (Since 1.0.3)
String convertWikiToStorageFormat(String token, String markup) - converts wiki markup to the storage format and returns it. (Since
4.0)
Spaces
Retrieval
Vector<SpaceSummary> getSpaces(String token) - returns all the SpaceSummaries that the current user can see.
Space getSpace(String token, String spaceKey) - returns a single Space. If the spaceKey does not exist: earlier versions of
Confluence will throw an Exception. Later versions (3.0+) will return a null object.
String exportSpace(String token, String spaceKey, String exportType) - exports a space and returns a String holding the URL for the
download. The export type argument indicates whether or not to export in XML or HTML format - use "TYPE_XML" or
"TYPE_HTML" respectively. Also, using "all" will select TYPE_XML.
In Confluence 3.0, the remote API specification for PDF exports changed. You can no longer use this 'exportSpace' method to export a
space to PDF. Please refer to Remote API Specification for PDF Export for current remote API details on this feature.
Management
Space addSpace(String token, Space space) - create a new space, passing in name, key and description.
Boolean removeSpace(String token, String spaceKey) - remove a space completely.
Space addPersonalSpace(String token, Space personalSpace, String userName) - add a new space as a personal space.
boolean convertToPersonalSpace(String token, String userName, String spaceKey, String newSpaceName, boolean updateLinks) convert an existing space to a personal space.
Space storeSpace(String token, Space space) - create a new space if passing in a name, key and description or update the
properties of an existing space. Only name, homepage or space group can be changed.
boolean importSpace(String token, byte[] zippedImportData) - import a space into Confluence. Note that this uses a lot of memory
- about 4 times the size of the upload. The data provided should be a zipped XML backup, the same as exported by Confluence.
ContentSummaries getTrashContents(String token, String spaceKey, int offset, int count) - get the contents of the trash for the given
space, starting at 'offset' and returning at most 'count' items.
boolean purgeFromTrash(String token, String spaceKey, long contentId) - remove some content from the trash in the given space,
deleting it permanently.
boolean emptyTrash(String token, String spaceKey) - remove all content from the trash in the given space, deleting them
permanently.
Pages
Retrieval
Vector<PageSummary> getPages(String token, String spaceKey) - returns all the PageSummaries in the space. Doesn't include
pages which are in the Trash. Equivalent to calling Space.getCurrentPages().
Page getPage(String token, Long pageId) - returns a single Page
Page getPage(String token, String spaceKey, String pageTitle) - returns a single Page
Vector<PageHistorySummary> getPageHistory(String token, String pageId) - returns all the PageHistorySummaries - useful for
looking up the previous versions of a page, and who changed them.
Permissions
Vector<ContentPermissionSet> getContentPermissionSets(String token, String contentId) - returns all the page level permissions for
this page as ContentPermissionSets\
Hashtable getContentPermissionSet(String token, String contentId, String permissionType) - returns the set of permissions on a
page as a map of type to a list of ContentPermission, for the type of permission which is either 'View' or 'Edit'
boolean setContentPermissions(String token, String contentId, String permissionType, Vector permissions) - sets the page-level
permissions for a particular permission type (either 'View' or 'Edit') to the provided vector of ContentPermissions. If an empty list of
permissions are passed, all page permissions for the given type are removed. If the existing list of permissions are passed, this
method does nothing.
Dependencies
Vector<Attachment> getAttachments(String token, String pageId) - returns all the Attachments for this page (useful to point users to
download them with the full file download URL returned).
Vector<PageSummary> getAncestors(String token, String pageId) - returns all the ancestors (as PageSummaries) of this page
(parent, parent's parent etc).
Vector<PageSummary> getChildren(String token, String pageId) - returns all the direct children (as PageSummaries\) of this page.
Vector<PageSummary> getDescendents(String token, String pageId) - returns all the descendents (as PageSummaries\) of this
page (children, children's children etc).
Vector<Comment> getComments(String token, String pageId) - returns all the comments for this page.
Comment getComment(String token, String commentId) - returns an individual comment.
Comment addComment(String token, Comment comment) - adds a comment to the page.
boolean removeComment(String token, String commentId) - removes a comment from the page.
Management
Page storePage(String token, Page page) - adds or updates a page. For adding, the Page given as an argument should have space,
title and content fields at a minimum. For updating, the Page given should have id, space, title, content and version fields at a
minimum. The parentId field is always optional. All other fields will be ignored. Note: the return value can be null, if an error that did
not throw an exception occurred. Operates exactly like updatePage() if the page already exists.
Page updatePage(String token, Page page, PageUpdateOptions pageUpdateOptions) - updates a page. The Page given should
have id, space, title, content and version fields at a minimum. The parentId field is always optional. All other fields will be ignored.
Note: the return value can be null, if an error that did not throw an exception occurred.
String renderContent(String token, String spaceKey, String pageId, String content) - returns the HTML rendered content for this
page. If 'content' is provided, then that is rendered as if it were the body of the page (useful for a 'preview page' function). If it's not
provided, then the existing content of the page is used instead (ie useful for 'view page' function).
String renderContent(String token, String spaceKey, String pageId, String content, Hashtable parameters) - Like the above
renderContent(), but you can supply an optional hash (map, dictionary, etc) containing additional instructions for the renderer.
Currently, only one such parameter is supported:
"style = clean" Setting the "style" parameter to "clean" will cause the page to be rendered as just a single block of HTML
within a div, without the HTML preamble and stylesheet that would otherwise be added.
void removePage(String token, String pageId) - removes a page
void movePage(String sourcePageId, String targetPageId, String position) - moves a page's position in the hierarchy.
sourcePageId - the id of the page to be moved.
targetPageId - the id of the page that is relative to the sourcePageId page being moved.
position - "above", "below", or "append". (Note that the terms 'above' and 'below' refer to the relative vertical position of the
pages in the page tree.)
void movePageToTopLevel(String pageId, String targetSpaceKey) - moves a page to the top level of the target space. This
corresponds to PageManager - movePageToTopLevel.
Position Keys for Moving a Page
Position Key
Effect
above
source and target become/remain sibling pages and the source is moved above the target in the page tree.
below
source and target become/remain sibling pages and the source is moved below the target in the page tree.
append
source becomes a child of the target
Attachments
Retrieval
Attachment getAttachment(String token, String pageId, String fileName, String versionNumber) - get information about an
attachment.
byte[] getAttachmentData(String token, String pageId, String fileName, String versionNumber) - get the contents of an attachment.
To retrieve information or content from the current version of an attachment, use a 'versionNumber' of "0".
Management
Attachment addAttachment(String token, long contentId, Attachment attachment, byte[] attachmentData) - add a new attachment to
a content entity object. Note that this uses a lot of memory - about 4 times the size of the attachment. The 'long contentId' is
actually a String pageId for XML-RPC.
boolean removeAttachment(String token, String contentId, String fileName) - remove an attachment from a content entity object.
boolean moveAttachment(String token, String originalContentId, String originalName, String newContentEntityId, String newName) move an attachment to a different content entity object and/or give it a new name.
Blog Entries
Vector<BlogEntrySummary> getBlogEntries(String token, String spaceKey) - returns all the BlogEntrySummaries\ in the space.
BlogEntry getBlogEntry(String token, String pageId) - returns a single BlogEntry.
BlogEntry storeBlogEntry(String token, BlogEntry entry) - add or update a blog entry. For adding, the BlogEntry given as an
argument should have space, title and content fields at a minimum. For updating, the BlogEntry given should have id, space, title,
content and version fields at a minimum. All other fields will be ignored.
BlogEntry getBlogEntryByDayAndTitle(String token, String spaceKey, int dayOfMonth, String postTitle) - Retrieves a blog post by
specifying the day it was published in the current month, its title and its space key.
BlogEntry getBlogEntryByDateAndTitle(String token, String spaceKey, int year, int month, int dayOfMonth, String postTitle) - retrieve
a blog post by specifying the date it was published, its title and its space key.
Notifications
The notification methods in the remote API are available in Confluence 3.5 and later.
boolean watchPage(String token, long pageId) - watch a page or blog post as the current user, returns false if a space, page or blog
is already being watched
boolean watchSpace(String token, String spaceKey) - watch a space as the current user, returns false if the space is already
watched
boolean watchPageForUser(String token, long pageId, String username) - add a watch on behalf of another user (space
administrators only)
boolean isWatchingPage(String token, long pageId, String username) - check whether a user is watching a page (space
administrators only, if the username isn't the current user)
boolean isWatchingSpace(String token, String spaceKey, String username) - check whether a user is watching a space (space
administrators only, if the username isn't the current user)
Vector<User> getWatchersForPage(String token, long pageId) - return the watchers for the page (space administrators only)
Vector<User> getWatchersForSpace(String token, String spaceKey) - return the watchers for the space (space administrators only).
Search
Vector<SearchResult> search(String token, String query, int maxResults) - return a list of SearchResults\ which match a given
search query (including pages and other content types). This is the same as a performing a parameterised search (see below) with
an empty parameter map.
Vector<SearchResult> search(String token, String query, Map parameters, int maxResults) - (since 1.3) like the previous search,
but you can optionally limit your search by adding parameters to the parameter map. If you do not include a parameter, the default is
used instead.
Parameters for Limiting Search Results
key
description
values
default
spaceKey
search a single space
(any valid space
key)
Search all
spaces
type
Limit the content types of the items to be returned in the search results.
page
blogpost
mail
comment
attachment
spacedescription
personalinformation
Search all types
modified
Search recently modified content
TODAY
YESTERDAY
LASTWEEK
LASTMONTH
No limit
contributor
The original creator or any editor of Confluence content. For mail, this is the person
who imported the mail, not the person who sent the email message.
Username of a
Confluence user.
Results are not
filtered by
contributor
Security
Vector<String> getPermissions(String token, String spaceKey) - Returns a Vector of Strings representing the permissions the current
user has for this space (a list of "view", "modify", "comment" and / or "admin").
Vector<String> getPermissionsForUser(String token, String spaceKey, String userName) - Returns a Vector of Strings representing
the permissions the given user has for this space. (since 2.1.4)
Vector<Permission> getPagePermissions(String token, String pageId) - Returns a Vector of Permissions\ representing the
permissions set on the given page.
Vector<String> getSpaceLevelPermissions(String token) - returns all of the space level permissions which may be granted. This is a
list of possible permissions to use with addPermissionToSpace, below, not a list of current permissions on a Space.
boolean addPermissionToSpace(String token, String permission, String remoteEntityName, String spaceKey) - Give the entity
named remoteEntityName (either a group or a user) the permission permission on the space with the key spaceKey.
boolean addPermissionsToSpace(String token, Vector permissions, String remoteEntityName, String spaceKey) - Give the entity
named remoteEntityName (either a group or a user) the permissions permissions on the space with the key spaceKey.
boolean removePermissionFromSpace(String token, String permission, String remoteEntityName, String spaceKey) - Remove the
permission permission} from the entity named {{remoteEntityName (either a group or a user) on the space with the
key spaceKey.
boolean addAnonymousPermissionToSpace(String token, String permission, String spaceKey) - Give anonymous users the
permission permission on the space with the key spaceKey. (since 2.0)
boolean addAnonymousPermissionsToSpace(String token, Vector permissions, String spaceKey) - Give anonymous users the
permissions permissions on the space with the key spaceKey. (since 2.0)
boolean removeAnonymousPermissionFromSpace(String token, String permission,String spaceKey) - Remove the permission
permission} from anonymous users on the space with the key {{spaceKey. (since 2.0)
boolean removeAllPermissionsForGroup(String token, String groupname) - Remove all the global and space level permissions for
groupname.
Space permissions
Names are as shown in Space Admin > Permissions. Values can be passed to security remote API methods above which take a space
permission parameter.
Permission name
String value
Description
View
VIEWSPACE
View all content in the space
Pages - Create
EDITSPACE
Create new pages and edit existing ones
Pages - Export
EXPORTPAGE
Export pages to PDF, Word
Pages - Restrict
SETPAGEPERMISSIONS
Set page-level permissions
Pages - Remove
REMOVEPAGE
Remove pages
News - Create
EDITBLOG
Create news items and edit existing ones
News - Remove
REMOVEBLOG
Remove news
Comments - Create
COMMENT
Add comments to pages or news in the space
Comments - Remove
REMOVECOMMENT
Remove the user's own comments
Attachments - Create
CREATEATTACHMENT
Add attachments to pages and news
Attachments - Remove
REMOVEATTACHMENT
Remove attachments
Mail - Remove
REMOVEMAIL
Remove mail
Space - Export
EXPORTSPACE
Export space to HTML or XML
Space - Admin
SETSPACEPERMISSIONS
Administer the space
In Confluence 3.0, the remote API specification for PDF exports changed. Consequently, the 'Space - Export' permission above no
longer applies to PDF exports. Please refer to Remote API Specification for PDF Export for current remote API details on this feature.
User Management
User getUser(String token, String username) - get a single user
void addUser(String token, User user, String password) - add a new user with the given password
void addGroup(String token, String group) - add a new group
Vector<String> getUserGroups(String token, String username) - get a user's current groups
void addUserToGroup(String token, String username, String groupname) - add a user to a particular group
boolean removeUserFromGroup(String token, String username, String groupname) - remove a user from a group.
boolean removeUser(String token, String username) - delete a user.
boolean removeGroup(String token, String groupname, String defaultGroupName) - remove a group. If defaultGroupName is
specified, users belonging to groupname will be added to defaultGroupName.
Vector<String> getGroups(String token) - gets all groups
boolean hasUser(String token, String username) - checks if a user exists
boolean hasGroup(String token, String groupname) - checks if a group exists
boolean editUser(String token, RemoteUser remoteUser) - edits the details of a user
boolean deactivateUser(String token, String username) - deactivates the specified user
boolean reactivateUser(String token, String username) - reactivates the specified user
Vector<String> getActiveUsers(String token, boolean viewAll) - returns all registered users
boolean setUserInformation(String token, UserInformation userInfo) - updates user information
UserInformation getUserInformation(String token, String username) - Retrieves user information
boolean changeMyPassword(String token, String oldPass, String newPass) - changes the current user's password
boolean changeUserPassword(String token, String username, String newPass) - changes the specified user's password
boolean addProfilePicture(String token, String userName, String fileName, String mimeType, byte[] pictureData) - add and set the
profile picture for a user.
Labels
Vector getLabelsById(String token, long objectId) - Returns all Labels\ for the given ContentEntityObject ID
Vector getMostPopularLabels(String token, int maxCount) - Returns the most popular Labels\ for the Confluence instance, with a
specified maximum number.
Vector getMostPopularLabelsInSpace(String token, String spaceKey, int maxCount) - Returns the most popular Labels\ for the given
spaceKey, with a specified maximum number of results.
Vector getRecentlyUsedLabels(String token, int maxResults) - Returns the recently used Labels\ for the Confluence instance, with a
specified maximum number of results.
Vector getRecentlyUsedLabelsInSpace(String token, String spaceKey, int maxResults) - Returns the recently used Labels\ for the
given spaceKey, with a specified maximum number of results.
Vector getSpacesWithLabel(String token, String labelName) - Returns an array of Spaces\ that have been labelled with labelName.
Vector getRelatedLabels(String token, String labelName, int maxResults) - Returns the Labels\ related to the given label name, with
a specified maximum number of results.
Vector getRelatedLabelsInSpace(String token, String labelName, String spaceKey, int maxResults) - Returns the Labels\ related to
the given label name for the given spaceKey, with a specified maximum number of results.
Vector getLabelsByDetail(String token, String labelName, String namespace, String spaceKey, String owner) - Retrieves the Labels\
matching the given labelName, namespace, spaceKey or owner.
Vector getLabelContentById(String token, long labelId) - Returns the content for a given label ID
Vector getLabelContentByName(String token, String labelName) - Returns the content for a given label name.
Vector getLabelContentByObject(String token, Label labelObject) - Returns the content for a given Label object.
Vector getSpacesContainingContentWithLabel(String token, String labelName) - Returns all Spaces\ that have content labelled with
labelName.
boolean addLabelByName(String token, String labelName, long objectId) - Adds label(s) to the object with the given
ContentEntityObject ID. For multiple labels, labelName should be in the form of a space-separated or comma-separated string.
boolean addLabelById(String token, long labelId, long objectId) - Adds a label with the given ID to the object with the given
ContentEntityObject ID.
boolean addLabelByObject(String token, Label labelObject, long objectId) - Adds the given label object to the object with the given
ContentEntityObject ID.
boolean addLabelByNameToSpace(String token, String labelName, String spaceKey) - Adds a label to the object with the given
ContentEntityObject ID.
boolean removeLabelByName(String token, String labelName, long objectId) - Removes the given label from the object with the
given ContentEntityObject ID.
boolean removeLabelById(String token, long labelId, long objectId) - Removes the label with the given ID from the object with the
given ContentEntityObject ID.
boolean removeLabelByObject(String token, Label labelObject, long objectId) - Removes the given label object from the object with
the given ContentEntityObject ID.
boolean removeLabelByNameFromSpace(String token, String labelName, String spaceKey) - Removes the given label from the
given spaceKey.
Data Objects
Most returned structs have a summary and a detailed form:
The summary form is a primary key (ie space key, page id) and a representative form (ie space name, page title)
The detailed form will have all of the entity details as might be needed for the client.
Unless otherwise specified, all returned structs are in detailed form.
ServerInfo
Key
Type
Value
majorVersion
int
the major version number of the Confluence instance
minorVersion
int
the minor version number of the Confluence instance
patchLevel
int
the patch-level of the Confluence instance
buildId
String
the build ID of the Confluence instance (usually a number)
developmentBuild
Boolean
Whether the build is a developer-only release or not
baseUrl
String
The base URL for the confluence instance
Note: Version 1.0.3 of Confluence would be major-version: 1, minor-version: 0, patch-level: 3. Version 2.0 would have a patch-level of 0,
even if it's not visible in the version number.
SpaceSummary
Key
Type
Value
key
String
the space key
name
String
the name of the space
type
String
type of the space
url
String
the url to view this space online
Space
Key
Type
Value
key
String
the space key
name
String
the name of the space
url
String
the url to view this space online
homePage
String
the id of the space homepage
description
String
the HTML rendered space description
PageSummary
Key
Type
Value
id
String
the id of the page
space
String
the key of the space that this page belongs to
parentId
String
the id of the parent page
title
String
the title of the page
url
String
the url to view this page online
locks
int
the number of locks current on this page
Page
Key
Type
Value
id
String
the id of the page
space
String
the key of the space that this page belongs to
parentId
String
the id of the parent page
title
String
the title of the page
url
String
the url to view this page online
version
int
the version number of this page
content
String
the page content
created
Date
timestamp page was created
creator
String
username of the creator
modified
Date
timestamp page was modified
modifier
String
username of the page's last modifier
homePage
Boolean
whether or not this page is the space's homepage
locks
int
the number of locks current on this page
contentStatus
String
status of the page (eg. current or deleted)
current
Boolean
whether the page is current and not deleted
PageUpdateOptions
Key
Type
Value
versionComment
String
Edit comment for the updated page
minorEdit
Boolean
Is this update a 'minor edit'? (default value: false)
PageHistorySummary
Key
Type
Value
id
String
the id of the historical page
version
int
the version of this historical page
modifier
String
the user who made this change
modified
Date
timestamp change was made
versionComment
String
the comment made when the version was changed
BlogEntrySummary
Key
Type
Value
id
String
the id of the blog entry
space
String
the key of the space that this blog entry belongs to
title
String
the title of the blog entry
url
String
the url to view this blog entry online
locks
int
the number of locks current on this page
publishDate
Date
the date the blog post was published
BlogEntry
Key
Type
Value
id
String
the id of the blog entry
space
String
the key of the space that this blog entry belongs to
title
String
the title of the page
url
String
the url to view this blog entry online
version
int
the version number of this blog entry
content
String
the blog entry content
locks
int
the number of locks current on this page
RSS Feed
Key
Type
Value
url
String
the URL of the RSS feed
title
String
the feed's title
Search Result
Key
Type
Value
title
String
the feed's title
url
String
the remote URL needed to view this search result online
excerpt
String
a short excerpt of this result if it makes sense
type
String
the type of this result - page, comment, spacedesc, attachment, userinfo, blogpost, status
id
String
the long ID of this result (if the type has one)
Attachment
Key
Type
Value
id
String
numeric id of the attachment
pageId
String
page ID of the attachment
title
String
title of the attachment
fileName
String
file name of the attachment (Required)
fileSize
String
numeric file size of the attachment in bytes
contentType
String
mime content type of the attachment (Required)
created
Date
creation date of the attachment
creator
String
creator of the attachment
url
String
url to download the attachment online
comment
String
comment for the attachment (Required)
Comment
Key
Type
Value
id
String
numeric id of the comment
pageId
String
page ID of the comment
title
String
title of the comment
content
String
notated content of the comment (use renderContent to render)
url
String
url to view the comment online
created
Date
creation date of the attachment
creator
String
creator of the attachment
User
Key
Type
Value
name
String
the username of this user
fullname
String
the full name of this user
email
String
the email address of this user
url
String
the url to view this user online
ContentPermission
Key
Type
Value
type
String
The type of permission. One of 'View' or 'Edit'
userName
String
The username of the user who is permitted to see or edit the content. Null if this is a group permission.
groupName
String
The name of the group who is permitted to see or edit the content. Null if this is a user permission.
ContentPermissionSet
Key
Type
Value
type
String
The type of permission. One of 'View' or 'Edit'
contentPermissions
List
The permissions. Each item is a ContentPermission.
Label
Key
Type
Value
name
String
the nameof the label
owner
String
the username of the owner
namespace
String
the namespace of the label
id
String
the ID of the label
UserInformation
Key
Type
Value
username
String
the username of this user
content
String
the user description
creatorName
String
the creator of the user
lastModifierName
String
the url to view this user online
version
int
the version
id
String
the ID of the user
creationDate
Date
the date the user was created
lastModificationDate
Date
the date the user was last modified
ClusterInformation
Key
Type
Value
isRunning
boolean
true if this node is part of a cluster.
name
String
the name of the cluster.
memberCount
int
the number of nodes in the cluster, including this node (this will be zero if this node is not clustered.)
description
String
a description of the cluster.
multicastAddress
String
the address that this cluster uses for multicasr communication.
multicastPort
String
the port that this cluster uses for multicast communication.
NodeStatus
Key
Type
Value
nodeId
int
an integer uniquely identifying the node within the cluster.
jvmStats
Map
a Map containing attributes about the JVM memory usage of node. Keys are "total.memory", "free.memory",
"used.memory".
props
Map
a Map containing attributes of the node. Keys are "system.date", "system.time", "system.favourite.colour",
"java.version", "java.vendor",
"jvm.version", "jvm.vendor", "jvm.implemtation.version", "java.runtime", "java.vm", "user.name.word", "user.timezone",
"operating.system", "os.architecture", "fs.encoding".
buildStats
Map
a Map containing attributes of the build of Confluence running on the node. Keys are "confluence.home",
"system.uptime", "system.version",
"build.number".
ContentSummaries
Key
Type
Value
totalAvailable
int
The total number of content available to be retrieved.
offset
int
The index of the first content retrieved.
content
Vector of ContentSummary
list of the retrieved content.
ContentSummary
Key
Type
Value
id
String
The ID of the content.
type
String
The type of content (e.g. "page", "comment", "blog").
space
String
The key of the space to which the content belongs.
status
String
The current status of the content (e.g. "current", "deleted").
title
String
The title of the content.
created
Date
Timestamp page was created.
creator
String
Username of the creator.
modified
Date
Timestamp content was modified.
modifier
String
Username of content's last modifier.
Script Examples
The Confluence User Community space contains various examples of scripts.
Changelog
Confluence 4.0
Added version 2 of the remote API.
Calls to methods that return page content using the version 1 API will now result in an error. Please use the version 2 API instead.
Added convertWikiToStorageFormat(String token, String markup) method.
Confluence 3.5
Added notification methods: watchPage, watchSpace, watchPageForUser, getWatchersForPage, getWatchersForSpace
Added blog post retrieval method: getBlogEntryByDateAndTitle
Added space management methods: getTrashContents, purgeFromTrash, emptyTrash.
Added data objects: ContentSummaries, ContentSummary.
Confluence 2.10
Added updatePage.
Confluence 2.9
Search: Removed option 'all' in table of content types and changed the default to 'All'. If you need to search for all types, simply omit
the 'type' restriction.
Search: Added option 'contributor' to the table of filter options.
Confluence 2.3
Added getClusterInformation and getClusterNodeStatuses.
Confluence 2.2
Added addPersonalSpace, convertToPersonalSpace and addProfilePicture.
Confluence 2.1.4
Added getPermissionsForUser.
Confluence 2.0
Updated getLocks() to getPagePermissions()
Added addAttachment, getAttachment, getAttachmentData, removeAttachment and moveAttachment methods to allow
remote attachment handling. Note that adding large attachments with this API uses a lot of memory during the addAttachment
operation.
Added addAnonymousPermissionToSpace, addAnonymousPermissionsToSpace and
removeAnonymousPermissionFromSpace.
Added the addComment and removeComment methods for comment manipulation.
Added hasGroup and hasUser methods to determine if a group or user exists.
Added editUser method.
Added ability to deactivate and reactivate users.
Added getActiveUsers method to retrieve a user list.
Added ability to change the user password.
Added ability to retrieve and modify user information.
Added ability to retrieve, add and remove labels.
Added getBlogEntryByDayAndTitle
Confluence 1.4
Added new exportSpace and exportSite methods to build exports of an individual space or an entire Confluence instance and
return with a URL leading to the download.
Added new getChildren and getDescendents methods to get the direct children and all descendents of a given page.
Added new getAncestors method to get the ancestors of a given page.
Removed the old getLocks as locks are superceded by page level permissions.
Added new getPagePermissions method to retrieve page level permissions.
Added new removeUser, removeGroup, removeAllPermissionsForGroup, addUserToGroup and removeUserFromGroup
methods.
Added new addPermissionToSpace method.
Added new Permission data object.
Added new getSpaceLevelPermissions method.
Confluence 1.3
Added new getPage method which retrieves a page by space key and page title.
Added new removeSpace method to remove an entire space.
Added ability to limit search by parameters.
Allow anonymous access.
Confluence 1.2
renderContent takes an optional hashtable for rendering hints, the only one supported right now is "style=clean"
Confluence 1.1
getLocks gives you back a list of any locks that apply to a given page
added a locks field to the various Page structs containing a count of any applicable page-level locks
CRUD methods added for blog-posts
Confluence 1.0.3
getServerInfo gives you some basic information about the server version CONF1123
storePage now allows you to change the page's name (incoming links are all renamed) CONF-974
storePage now reliably allows you to re-parent pages
WSDL now respects the server's configured base URL, allowing it to work on proxy-hosted servers CONF-1088
RELATED TOPICS
Confluence Plugin Guide
Confluence User Macro Guide
Confluence Remote APIs
Development Resources
Development Resources
Building Confluence From Source Code
Confluence Architecture
Confluence Developer FAQ
Confluence Developer Forum
Preparing for Confluence 4.0
Building Confluence From Source Code
This guide describes building a confluence.war distribution or an IDE project from the Confluence source code. Plugin developers who
wish to use source code as an aid in building plugins should refer to the plugin documentation. This process should be simple and quick, so
please let us know if you get stuck.
Building a WAR distribution
1. Download Confluence source code.
1.
Source code access is available for commercial license holders. If you do not have access to the source code
download site, log in to my.atlassian.com as your billing contact or contact our sales department.
Please be aware that while Confluence's source code is available to licensed customers, this does not apply to the
Confluence Office Connector.
2. Confluence is built using Maven. Maven is bundled with the source distribution and therefore does not need to be installed
separately. When you build Confluence, Maven will download dependencies and store them in a local repository. One of these
dependencies requires manual installation for legal distribution reasons. If you do not already have it in your private repository,
download JavaMail from Sun's website.
Sun will not allow Maven to redistribute its binaries. You must install all Sun binaries manually by downloading
them from Sun's website and running the mvn install command. Maven has provided documentation for both
3rd party jars in general and Sun jars in particular.
Unzip the mail.jar file from the javamail-1_x_x.zip file. From the root of your extracted source code directory, run the following
command, where Path/To/mail.jar is the absolute path to the extracted mail.jar file.:
.\maven\bin\mvn install:install-file -DgroupId=javax.mail -DartifactId=mail -Dversion=1.x.x
-Dpackaging=jar -Dfile=Path/To/mail.jar
3. Run your build script.
If the build is run successfully you should have a confluence.war file created in ../<confluence-project>/dist/.
Option 1: Building the Confluence Project or Individual Libraries using IDEA
This is the simplest option. From IDEA, go to File >> Open Project. Browse to the pom.xml file of the individual project. If you are wanting to
compile the Confluence project (as opposed to one of the libraries, say Atlassian User), use the pom.xml file from the confluence-project file.
Using the pom.xml at the root of the distribution to load the Confluence project and all its dependencies usually results in
classloading errors. If you want to debug a dependency and the confluence core together, you'll have to integrate the
projects.
Option 2: Building the Confluence Project or Individual Libraries Using Maven
Each Confluence Library is bundled with its own Maven pom file. To build one of the sub-projects, you need not build the entire source. To
use the bundled maven distribution:
1. Copy build.sh or build.bat to the appropriate sub-directory.
2. Change M2_HOME to point to the parent directory, as so:
build.sh:
export M2_HOME="$PWD/../maven
build.bat:
set M2_HOME=..\maven
Building an Intellij Idea or an Eclipse project
1. To build a project for an IDE, you can use the instructions above, but modify the build.sh or build.bat mvn command. Replace:
exec mvn clean package -Dmaven.test.skip $* with:
exec mvn idea:idea or exec mvn eclipse:eclipse
A great way to open an IDEA module is to import the pom file from within IDEA. You can do it from File >>
Open Project >> {browse to the pom file to import. You'll want to open the confluence-project pom
or other module.
This should leave a project file in the root of your source directory. It should have all the confluence modules.
1. Download Intellij IDEA.
2. Install Tomcat and get it running.
3. In your Confluence source tree, edit
confluence-project/conf-webapp/src/main/resources/confluence-init.properties. Set your home
directory.
4. From Preferences > Application Servers add Tomcat
5. From Run > Edit Configurations, add a Tomcat Configuration. Select to deploy the confluence-webapp module to the
appserver:
6. Click Configure and configure how to deploy. Choose to Create web module exploded directory and exclude from module content:
7. From the server tab, you might set some memory settings like:
-XX:MaxPermSize=256M -Xmx512m
8. Run the application. Have fun!
Creating a Single Patch File
If you'd like to create a patch:
1. If you haven't done so, select Build >> Make Project.
2. Select Build >> Compile '<class name>.java'
This will leave a compiled class file in the <confluence-source distribution>/confluence-project/confluence/target/classes/<path-to-class>
where the path to class is the package of the class you've compiled.
Creating a Server for Eclipse
In Eclipse creating a server defines creating a pointer to an existing installation of an application server.
To create a server:
Window->Show View->Servers
Right Click and select New->Server
In the menu bar click File->New->Other and expand the server folder and select the version of the serer you have installed on your system.
Click Next and the New Server wizard opens. This wizard defines a new server, that contains information required to point to a specific
runtime environment for local or remote testing, or for publishing to an application server. By default, this field is pre-filled with the default
address: localhost
Supported Servers in Eclipse:
1. Eclipse view after adding Tomcat
Troubleshooting and Technical Support
If you get a class not found error, you may need to replace a jar file in your maven repository. Try our forums.
Atlassian encourages our community to make use of our source code. Please be aware that upgrades may require additional modifications.
Source code modifications are not supported by Atlassian Support.
RELATED TOPICS:
FAQ and Troubleshooting
How to Build an Atlassian Plugin
Working with Sun Java Libraries
Confluence Architecture
Introduction
These pages are internal developer documentation for Confluence. The main audience for these documents is Atlassian developers, but
hopefully plugin and extension developers might benefit from knowing more about how the application works. There are, however, a few
caveats:
1. This documentation is incomplete. All system documentation is a work in progress, and more documents will come online as they
are written. (This is, after all, a wiki.)
2. Confluence has been in development since 2003, much longer than these documents have existed. There are many parts of the
application that do not follow these guidelines, and some of the architecture documents represent how things should be from now on
rather than how they were in the past
Understanding Confluence
These documents should give you some understanding of how the Confluence code-base is structured, where to find things, and where to
put new things.
High Level Architecture Overview
Confluence Permissions Architecture
Developer Guidelines
These documents are more general descriptions of How We Do Things Around Here. It's a good idea to be familiar with these documents,
but keep in mind that no rule is set in stone, and the existence of a guideline does not absolve you from your responsibility to think.
Spring Usage Guidelines
Exception Handling Guidelines
Logging Guidelines
Deprecation Guidelines
Hibernate Sessions and Transaction Management Guidelines
Javadoc Standards
Anti-XSS documentation
This feature is present in Confluence 2.9 and later
This documentation is aimed at developers. It contains information necessary to get Velocity template rendering to function correctly when
Confluence has the "Anti XSS mode" option enabled. This option is disabled by default in Confluence 2.9 to allow plugin developers time to
adapt for any incompatibilities in their plugins and to shake out any problems in the implementation, but we hope to make it standard and
mandatory in future releases.
What is Anti XSS mode?
Automatic reference encoding in Velocity templates
How does it work?
Opt-ing out of automatic HTML encoding
HtmlSafe method annotation
Method naming convention
Well known HTML returning methods
Reference naming convention
Migration strategy for template authors
Caveats
What is Anti XSS mode?
This mode will engage certain behaviours in Confluence intended to reduce the incidence of cross site scripting (XSS) vulnerabilities. At
present this mode enables an automatic data encoding strategy designed to reduce XSS exploits arising from the incorrect encoding of data
embedded in HTML templates.
Developers interested in extending this XSS protection feature to their plugins should consult the Enabling XSS Protection
in Plugins document.
Automatic reference encoding in Velocity templates
Many of the past XSS vulnerabilities in Confluence have arisen simply because data from untrusted sources have not been encoded
correctly when mixed with other HTML in Velocity templates. Such encoding failures lead to possible HTML injection attacks that can range
from breaking page rendering, to hijacking user sessions. These security bugs will always be easily introduced when template authors have
to make a conscious decision to specifically encode untrusted data when rendered. Other disadvantages of this opt-in security include a
proliferation of noise in templates related directly to encoding operations ($generalUtil.htmlEncode et al) and a general obscuration of
where data are being written unsafely to the client. In future releases of Confluence we will be attempting to transition to a new rendering
mode where all data will be HTML encoded by default unless steps are taken explicitly to avoid this behaviour in templates.
How does it work?
This new mode of behaviour takes advantage of two new facilities introduced into the Velocity
templating engines during the 1.4 and 1.5 releases. (Confluence originally shipped with Velocity 1.3 but
was upgraded to Velocity 1.5 in the 2.7 release). In short there are two parts of the system:
1. A mechanism for marking data as being safe for HTML rendering.
2. A mechanism for encoding any data not marked as safe as it is being written to the output.
Opt-ing out of automatic HTML encoding
While we'd recommend that as much of your HTML markup be contained in actual Velocity templates,
many templates acquire HTML markup via method calls and property access to Java objects in the
Velocity context and very often the result is written directly to the output. In this situation we need to
inform the Velocity renderer that these values are intended to contain HTML and should not be encoded
when written. There are a few ways to accomplish this.
HtmlSafe method annotation
For values retrieved by calling methods or accessing properties of objects in the context, it is possible to
inform the Velocity system that these values are safe to be written without encoding. This is achieved by
annotating the method (whether a property getter or not) with the HtmlSafe annotation.
An annotated Java class
import com.atlassian.confluence.velocity.htmlsafe.HtmlSafe;
public class MyContextClass
{
@HtmlSafe
public String myMarkupReturningMethod() {
return "<b>This method returns marked up text!</b>";
}
public String myMethodWithAnXssExploit() {
return "<script>alert('owned');</script>";
}
}
Using an instance of this class in a template
<ol>
<li>$objectOfMyContextClass.myMarkupReturningMethod()
<li>$objectOfMyContextClass.myMethodWithAnXssExploit()
</ol>
Result when Anti-XSS is disabled
<ol>
<li><b>This method returns marked up text!</b>
<li><script>alert('owned');</script>
</ol>
Result when Anti-XSS is enabled
<ol>
<li><b>This method returns marked up text!</b>
<li>&lt;script&gt;alert('owned');&lt;/script&gt;
</ol>
Method naming convention
Retrofitting this type of behaviour into an existing, significant codebase with an extensive plugin catalogue is very difficult and we'd like to
make this new behaviour fit in as well as possible with the existing body of work. For this reason certain methods will automatically be
deemed as being HtmlSafe:
Those that start with render or getRender
Those that end with Html
This strategy fits in with the observation that many of the existing methods that return HTML were named according to this convention. It also
provides a mechanism for avoiding automatic encoding where Java 5 annotations are not an option.
Well known HTML returning methods
A few often used methods are known to return HTML by contract. These methods are therefore also treated as HtmlSafe by default.
com.opensymphony.util.TextUtils#htmlEncode
com.opensymphony.webwork.util.VelocityWebWorkUtil#htmlEncode
This means that any uses of these methods will behave identically whether or not the anti-XSS mode is engaged. It is important to note that
GeneralUtil.htmlEncode() has been annotated as HtmlSafe and will also behave identically without any modification to uses in
templates.
Reference naming convention
To cater for cases where HTML strings are built entirely in a Velocity template and then rendered, it is possible to avoid the auto encoder by
using a "Html" suffix on the reference name.
Template
#set ($someHtml = "<p>This is a paragraph</p>")
#set ($some = $someHtml)
<ul>
<li>$someHtml
<li>$some
</ul>
Output
<ul>
<li><p>This is a paragraph</p>
<li>&lt;p&gt;This is a paragraph&lt;/p&gt;
</ul>
Transitional reference name exclusion
The velocity template reference $body will also avoid automatic encoding for the time being. Many templates use this convention to include
whole slabs of HTML sourced from other rendering mechanisms. This exclusion is very likely to be removed in the future so it is strongly
recommended that all such references be changed to make use of the standard "html" suffix as described previously.
Migration strategy for template authors
To ensure that your HTML markup will function correctly now and in the future here are some guidelines of working with the Anti-XSS
feature:
Don't stop using htmlEncode methods just yet – As the automatic HTML encoding feature is disabled by default it is still
necessary to make sure that any unsafe data is explicitly HTML encoded before being written. Encoding the data explicitly behaves
identically whether automatic HTML encoding is enabled or not. In the future we are hoping to make this the standard behaviour of
templates in Confluence, allowing template authors to remove all such explicit encoding calls from templates.
Try to move all of your HTML markup to Velocity templates – The more that your markup is contained in templates, the less
intrusive the automatic encoding system will be. This is a good design choice in general as markup in templates is far more
maintainable than static strings in Java classes.
Mark any other HTML data as being HtmlSafe – methods that return HTML markup that cannot be contained in templates such
as data sourced from user input or other remote retrieval need to be marked as HtmlSafe or assigned to a Velocity reference
ending in the string Html before use. Consider using the HtmlFragment class for a richer, HtmlSafe description of the data that
you are returning. The fewer sources of HtmlSafe data the better the security of the system.
Move away from relying on the transitional $body reference encoding exclusion – To keep the system as consistent as
possible, usages of $body in templates that include HTML fragements should be changed to use either a "html" suffix or the
HtmlFragment class.
Use the system property confluence.html.encode.automatic to test your templates – you can enable and disable the
automatic encoding functionality in Confluence via this command line JVM system property, handy for automated test suites.
Raise any issues you have – if you think we can do something better or make it easier for you to write templates and plugins that
support this new mechanism we'd love to hear from you.
Developers interested in more advanced details and use-cases should consult the Advanced HTML encoding documentation.
Caveats
As much as we'd love to make the new HTML encoding system transparent to use there are a few things that you need to watch out for.
Velocity string interpolation
The Velocity configuration of Confluence allows you to use reference interpolation in any strings you construct in a Velocity template.
#set ($myVar = "<p>Here is a paragraph</p>")
#set ($myHtml = "$myVar <p>A second paragraph</p>")
Here it is: $myHtml
Here it is: &lt;p&gt;Here is a paragraph&lt;/p&gt; <p>A second paragraph</p>
As can be seen from this example, automatic HTML encoding will occur when references are interpolated inside strings in the same manner
as when they are written to the template output. At present there is no way to treat this case specially and you will need to make sure that
any data used as part of interpolation is being treated correctly by the encoder.
String parameters
Occasionally you may have some code in your velocity template that makes a call back to some Java logic. To make sure that the value is
being protected by the Anti-XSS mechanism, you must have the string evaluate within the velocity template. If not, you are passing a
reference into the Java code which will not be protected.
You should write the velocity template like this:
$object.doSomething("$action.getValue()")
The quotes around the $action.getValue() call mean velocity will evaluate it before passing it into object.doSomething() and have
a chance to be automatically encoded before being passed to the Java method.
Accessing action context values
Templates rendered from a Webwork Velocity result are able to access values on Webwork's action stack as if they were entries in the
Velocity context. If these values are sourced from getter methods on the current action the automatic encoding system cannot detect whether
the getter method has been marked as HtmlSafe. In this situation the value will be automatically encoded when rendered regardless of any
annotation or method naming convention used by the source of the value.
To remedy this either use the HtmlSafe reference naming convention (e.g. assigning the action context value to a context variable ending
with Html before rendering) or retrieve the value directly from the current action via the $action reference.
Unexpected context types
Some Java code may use the Velocity context as a data passing mechanism to collect information from a template after it is rendered.
public class DataHolder {
@HtmlSafe
public String getHtml() {
return "<strong>My html</strong>";
}
}
myTemplate
#set ($result = data.getHtml())
...
Template myTemplate = getTemplate();
Context myContext = new VelocityContext();
myContext.put("data", new DataHolder());
renderTemplate(myTemplate, myContext);
String message = (String) myContext.get("result");
The above Java code will fail with a ClassCastException at runtime because the reference $result will not be an instance of String
but an instance of BoxedValue due to the way that Confluence's Velocity runtime handles HtmlSafe values in the Velocity context. If there
is demand it is feasible for type compatibility to be restored in this situation via the use of a transparent, unboxing context layer but in general
this mechanism of information passing is discouraged. Context values that are not set from HtmlSafe sources are not affected in this
situation.
Advanced HTML encoding
Advanced automatic encoding topics
Collection inheritance
In some cases a method may return a collection of values, each of which contain HTML markup to be treated literally in a Velocity template. If
a collection returning method is marked as HtmlSafe, then all of its members will be treated as HtmlSafe as well should they be written to
a Velocity template.
More precisely, a value retrieved from a HtmlSafe collection will be treated as HtmlSafe in the following situations:
The element is retrieved via:
java.util.List#get(index)
java.util.Map#get(key)
The element is reached via an iterator returned by java.util.Collection#iterator() (as is the case when a collection is
used with the Velocity #foreach statement.)
HtmlFragment
One of the things that has become painfully obvious in the implementation of this system is the inadequacy of the String class for returning
markup. The need for a HtmlSafe annotation is only necessary because a String return type does not convey any information about the
data returned other than it is a sequence of characters. The com.atlassian.confluence.velocity.htmlsafe.HtmlFragment class
is a type that can be used to indicate that the toString() method of a particular object returns markup to be interpreted as literal HTML
during rendering.
HtmlFragment aHtmlFragment = new HtmlFragment("<span>This string contains literal HTML</span">);
HtmlFragment anotherHtmlFragment = new HtmlFragment(new MyHtmlClass());
The HtmlFragment class' toString() method delegates to the wrapped object's toString() method and will be treated as being safe
for use in HTML templates. This is useful for adding your own HtmlSafe values directly to a Velocity context or as a return type for methods
returning HTML markup.
VelocityUtils HTML detection heuristic
The com.atlassian.confluence.utils.VelocityUtils class defines methods of the form getRenderedTemplate for convenient
rendering of a template to a String. Unfortunately the only information available to this mechanism is the template name and Velocity
context, neither of which indicate directly whether the template is to be used for HTML output. At present the rendering strategy used by
these methods will try to determine whether a template contains HTML-like markup and will only employ the auto encoding mechanism
should such markup be detected. It is likely that this method of template rendering will be deprecated in the short term and replaced by a
more expressive, safer system.
PossibleIncorrectHtmlEncodingEventHandler
To help track down any Velocity reference that might be encoded incorrectly, Confluence attaches a logging event listener to the Velocity
context. This event listener will print the reference name and the template that contains the reference if the reference value contains HTML
like markup (either element markup or encoded entities) but the reference hasn't been marked as HTML safe. Naturally some values may
contain HTML markup that are not meant to be interpreted as literal HTML, as is the case in HTML injection attacks.
This event listener will only be active if the log4j logger category
com.atlassian.confluence.velocity.htmlsafe.PossibleIncorrectHtmlEncodingEventHandler has been set to log at
INFO.
The boxing uberspect
Confluence employs a specialisation of Velocity's pluggable, federated introspection strategy known as an Uberspect. The Velocity runtime
delegates all method calls, property accesses and iterator strategy retrieval via the configured uberspect. The default Velocity uberspect
implementation provides all of the facilities to call methods and access properties of objects in the Velocity context by making heavy use of
the Java reflection API. It also provides the mechanism for case insensitive property access, Velocity's map getter syntactic sugar and uses
an internal introspection cache to optimise performance.
To enable the tracking of values retrieved from HtmlSafe sources, Confluence is configured to use a specialised uberspect that boxes any
ReturnValueAnnotation annotations with the method return value. Return values from method calls that do not have any such
annotations are treated exactly as before. Of course any of these uberspect boxed values may be subsequently used as arguments or
targets of other method calls, so the annotation boxing uberspect is also responsible for unboxing these values before a method is executed,
providing a mostly transparent operation.
For those developers who are more interested in the exact implementation details, you will want to examine the source of the classes in the
new com.atlassian.confluence.velocity.introspection package, with the AnnotationBoxingUberspect being the best
place to start.
REV 400 Advanced HTML encoding
This page is a draft in progress and visible to atlassian-staff only.
This page contains information about advanced HTML encoding for Confluence plugins.
On this page:
Advanced automatic encoding topics
Collection inheritance
HtmlFragment
VelocityUtils HTML detection heuristic
PossibleIncorrectHtmlEncodingEventHandler
The boxing uberspect
Advanced automatic encoding topics
Collection inheritance
In some cases a method may return a collection of values, each of which contain HTML markup to be treated literally in a Velocity template. If
a collection returning method is marked as HtmlSafe, then all of its members will be treated as HtmlSafe as well should they be written to
a Velocity template.
More precisely, a value retrieved from a HtmlSafe collection will be treated as HtmlSafe in the following situations:
The element is retrieved via:
java.util.List#get(index)
java.util.Map#get(key)
The element is reached via an iterator returned by java.util.Collection#iterator() (as is the case when a collection is
used with the Velocity #foreach statement.)
HtmlFragment
One of the things that has become painfully obvious in the implementation of this system is the inadequacy of the String class for returning
markup. The need for a HtmlSafe annotation is only necessary because a String return type does not convey any information about the
data returned other than it is a sequence of characters. The com.atlassian.confluence.velocity.htmlsafe.HtmlFragment class
is a type that can be used to indicate that the toString() method of a particular object returns markup to be interpreted as literal HTML
during rendering.
HtmlFragment aHtmlFragment = new HtmlFragment("<span>This string contains literal HTML</span">);
HtmlFragment anotherHtmlFragment = new HtmlFragment(new MyHtmlClass());
The HtmlFragment class' toString() method delegates to the wrapped object's toString() method and will be treated as being safe
for use in HTML templates. This is useful for adding your own HtmlSafe values directly to a Velocity context or as a return type for methods
returning HTML markup.
VelocityUtils HTML detection heuristic
The com.atlassian.confluence.utils.VelocityUtils class defines methods of the form getRenderedTemplate for convenient
rendering of a template to a String. Unfortunately the only information available to this mechanism is the template name and Velocity
context, neither of which indicate directly whether the template is to be used for HTML output. It is using HTML encoding by default, unless
you explicitly opt-out using the #disableAntiXSS() directive. Opting out is recommended only for troubleshooting purposes.
PossibleIncorrectHtmlEncodingEventHandler
To help track down any Velocity reference that might be encoded incorrectly, Confluence attaches a logging event listener to the Velocity
context. This event listener will print the reference name and the template that contains the reference if the reference value contains HTML
like markup (either element markup or encoded entities) but the reference hasn't been marked as HTML safe. Naturally some values may
contain HTML markup that are not meant to be interpreted as literal HTML, as is the case in HTML injection attacks.
This event listener will only be active if the log4j logger category
com.atlassian.confluence.velocity.htmlsafe.PossibleIncorrectHtmlEncodingEventHandler has been set to log at
INFO.
The boxing uberspect
Confluence employs a specialisation of Velocity's pluggable, federated introspection strategy known as an Uberspect. The Velocity runtime
delegates all method calls, property accesses and iterator strategy retrieval via the configured uberspect. The default Velocity uberspect
implementation provides all of the facilities to call methods and access properties of objects in the Velocity context by making heavy use of
the Java reflection API. It also provides the mechanism for case insensitive property access, Velocity's map getter syntactic sugar and uses
an internal introspection cache to optimise performance.
To enable the tracking of values retrieved from HtmlSafe sources, Confluence is configured to use a specialised uberspect that boxes any
ReturnValueAnnotation annotations with the method return value. Return values from method calls that do not have any such
annotations are treated exactly as before. Of course any of these uberspect boxed values may be subsequently used as arguments or
targets of other method calls, so the annotation boxing uberspect is also responsible for unboxing these values before a method is executed,
providing a mostly transparent operation.
For those developers who are more interested in the exact implementation details, you will want to examine the source of the classes in the
new com.atlassian.confluence.velocity.introspection package, with the AnnotationBoxingUberspect being the best
place to start.
Related topics
Anti-XSS documentation
Enabling XSS Protection in Plugins
Preventing XSS issues with macros in Confluence 4.0
Enabling XSS Protection in Plugins
This documentation is aimed at developers. In Confluence 3.0, Anti-XSS mode is enabled by default for the core Confluence application, but
in order to prevent this configuration change from breaking plugins, plugin authors must opt in to extend the protection to their own templates.
What is Anti-XSS?
Why should I have my plugin opt in to Anti-XSS protection?
How do I opt in to Anti-XSS protection?
A note on naming
What is Anti-XSS?
Anti-XSS is a safeguard placed on Velocity template files that automatically HTML encodes inserted variables, therefore protecting against
potential cross-site scripting vulnerabilities. It was introduced in Confluence 2.9, and enabled by default in Confluence 3.0. For more
information, read the Anti-XSS documentation.
Why should I have my plugin opt in to Anti-XSS protection?
Cross site scripting is a real and dangerous security problem with many web applications. Anti-XSS protects against many potential sources
of XSS vulnerabilities. Opting in to Anti-XSS protection requires very little effort, and results in a safer plugin.
Atlassian may make Anti-XSS apply automatically to Confluence plugin templates in the future, so by opting in now you save yourself from
your plugin maybe breaking in a future Confluence update.
How do I opt in to Anti-XSS protection?
There are three mechanisms to mark that your Velocity template should have Anti-XSS protection applied to it:
Give the template's filename a .html.vm suffix (i.e. mypage.html.vm)
Place the template in a directory called html (i.e. /templates/html/mypage.vm)
Put the Velocity directive call #htmlSafe() somewhere in the template.
If you do any (or any combination) of the above, any variable substitution performed in your Velocity template will be HTML-encoded under
the rules described in the Anti-XSS documentation.
A note on naming
You may notice that the #htmlSafe() velocity directive (which causes a template to opt in to Anti-XSS protection) has the opposite
meaning to the @HTMLSafe Java annotation (which causes a Java method to opt out of Anti-XSS protection). We regret this confusing
naming and hope to fix it in a future release, but unfortunately we didn't catch it in time to fix for 3.0. We will, however, ensure that
#htmlSafe() continues to work.
REV 400 Enabling XSS Protection in Plugins
This page is a draft in progress and visible to atlassian-staff only.
This documentation is for plugin developers.
In Confluence 4.0, the Anti-XSS protection for plugins is enabled by default, but in rare cases when this configuration change breaks an
existing plugin, plugin authors may need to take action to ensure that their plugin still works.
On this page:
What is Anti-XSS?
Why should I have my plugin opt in to Anti-XSS protection?
How do I opt in to Anti-XSS protection?
Why would I need my plugin opt out of Anti-XSS protection?
How do I opt out of Anti-XSS protection?
HtmlSafe method annotation
Method naming convention
Well known HTML returning methods
Reference naming convention
Migration strategies for template authors
Caveats
How does HTML encoding work?
A note on naming
What is Anti-XSS?
Anti-XSS is a safeguard placed on Velocity template files that automatically HTML encodes inserted variables, therefore protecting against
potential cross-site scripting vulnerabilities. It was introduced in Confluence 2.9, and enabled by default in Confluence 3.0 for core
Confluence, and then enforced for Confluence core and plugins in Confluence 4.0. It does not apply to any other HTML output generated by
plugins. For more information, read the Anti-XSS documentation.
The page REV400 Anti-XSS documentation does not exist.
Why should I have my plugin opt in to Anti-XSS protection?
Cross site scripting is a real and dangerous security problem with many web applications. Anti-XSS protects against many potential sources
of XSS vulnerabilities. Opting in to Anti-XSS protection requires very little effort, and results in a safer plugin.
Anti-XSS applies automatically to Confluence plugins by default in Confluence 4.0. By explicitly opting in, you may avoid your plugin
exposing XSS vulnerabilities if the Confluence admin setting 'Enforce Anti-XSS for plugins' is disabled.
How do I opt in to Anti-XSS protection?
There are three mechanisms to mark that your Velocity template should have Anti-XSS protection applied to it:
Give the template's filename a .html.vm suffix (i.e. mypage.html.vm)
Place the template in a directory called html (i.e. /templates/html/mypage.vm)
Put the Velocity directive call #htmlSafe() somewhere in the template.
If you do any (or any combination) of the above, any variable substitution performed in your Velocity template will be always HTML-encoded
under the rules described in the Anti-XSS documentation.
Why would I need my plugin opt out of Anti-XSS protection?
The enforced HTML-encoding may cause some plugins to stop functioning correctly. The symptoms include the following:
Raw HTML appears inline, when it should be rendered.
JavaScript functions don't activate due to double-encoding.
How do I opt out of Anti-XSS protection?
As of Confluence 4.0, HTML encoding is on for plugins by default.
While we'd recommend that as much of your HTML markup be contained in actual Velocity templates, some templates acquire HTML
markup via method calls and property access to Java objects in the Velocity context and very often the result is written directly to the output
of the template. In this situation we need to inform the Velocity renderer that these values are intended to contain HTML and should not be
encoded when written.
There are a few ways to accomplish this, as noted below.
HtmlSafe method annotation
For values retrieved by calling methods or accessing properties of objects in the context, it is possible to inform the Velocity system that
these values are safe to be written without encoding. This is achieved by annotating the method (whether a property getter or not) with the
HtmlSafe annotation.
An annotated Java class
import com.atlassian.confluence.velocity.htmlsafe.HtmlSafe;
public class MyContextClass
{
@HtmlSafe
public String myMarkupReturningMethod() {
return "<b>This method returns marked up text!</b>";
}
public String myMethodWithAnXssExploit() {
return "<script>alert('owned');</script>";
}
}
Using an instance of this class in a template
<ol>
<li>$objectOfMyContextClass.myMarkupReturningMethod()
<li>$objectOfMyContextClass.myMethodWithAnXssExploit()
</ol>
Result when Anti-XSS is disabled
<ol>
<li><b>This method returns marked up text!</b>
<li><script>alert('owned');</script>
</ol>
Result when Anti-XSS is enabled
<ol>
<li><b>This method returns marked up text!</b>
<li>&lt;script&gt;alert('owned');&lt;/script&gt;
</ol>
Method naming convention
Retrofitting this type of behaviour into an existing, significant codebase with an extensive plugin catalogue is very difficult and we'd like to
make this new behaviour fit in as well as possible with the existing body of work. For this reason certain methods will automatically be
deemed as being HtmlSafe:
Those that start with render or getRender
Those that end with Html
This strategy fits in with the observation that many of the existing methods that return HTML were named according to this convention.
Well known HTML returning methods
A few often used methods are known to return HTML by contract. These methods are therefore also treated as HtmlSafe by default.
com.opensymphony.util.TextUtils#htmlEncode
com.opensymphony.webwork.util.VelocityWebWorkUtil#htmlEncode
This means that any uses of these methods will behave identically whether or not the anti-XSS mode is engaged. It is important to note that
GeneralUtil.htmlEncode() has been annotated as HtmlSafe and will also behave identically without any modification to uses in
templates.
Reference naming convention
To cater for cases where HTML strings are built entirely in a Velocity template and then rendered, it is possible to avoid the auto encoder by
using a "Html" suffix on the reference name.
Template
#set ($someHtml = "<p>This is a paragraph</p>")
#set ($some = $someHtml)
<ul>
<li>$someHtml
<li>$some
</ul>
Output
<ul>
<li><p>This is a paragraph</p>
<li>&lt;p&gt;This is a paragraph&lt;/p&gt;
</ul>
Transitional reference name exclusion
The velocity template reference $body will also avoid automatic encoding for the time being. Many templates use this convention to include
whole slabs of HTML sourced from other rendering mechanisms. This exclusion is very likely to be removed in the future so it is strongly
recommended that all such references be changed to make use of the standard "html" suffix as described previously.
Using the 'Disable Anti-XSS' directive in a Velocity Template
Add the following velocity directive to your template:
#disableAntiXSS()
This will prevent variables in your template being HTML encoded automatically.
Migration strategies for template authors
To ensure that your HTML markup will function correctly now and in the future here are some guidelines of working with the Anti-XSS
feature:
Try to move all of your HTML markup to Velocity templates – The more that your markup is contained in templates, the less
intrusive the automatic encoding system will be. This is a good design choice in general as markup in templates is far more
maintainable than static strings in Java classes.
Mark any other HTML data as being HtmlSafe – methods that return HTML markup that cannot be contained in templates such
as data sourced from user input or other remote retrieval need to be marked as HtmlSafe or assigned to a Velocity reference
ending in the string Html before use. Consider using the HtmlFragment class for a richer, HtmlSafe description of the data that
you are returning. The fewer sources of HtmlSafe data the better the security of the system.
Move away from relying on the transitional $body reference encoding exclusion – To keep the system as consistent as
possible, usages of $body in templates that include HTML fragements should be changed to use either a "html" suffix or the
HtmlFragment class.
To test your plugins, change the admin setting XSS protection for plugins on the Confluence Admin > Security > Security
configuration page – you can enable and disable the automatic encoding functionality in Confluence via this setting.
Raise any issues you have – if you think we can do something better or make it easier for you to write templates and plugins that
support this new mechanism we'd love to hear from you.
Developers interested in more advanced details and use-cases should consult the Advanced HTML encoding documentation.
Caveats
As much as we'd love to make the new HTML encoding system transparent to use there are a few things that you need to watch out for.
Velocity string interpolation
The Velocity configuration of Confluence allows you to use reference interpolation in any strings you construct in a Velocity template.
#set ($myVar = "<p>Here is a paragraph</p>")
#set ($myHtml = "$myVar <p>A second paragraph</p>")
Here it is: $myHtml
Here it is: &lt;p&gt;Here is a paragraph&lt;/p&gt; <p>A second paragraph</p>
As can be seen from this example, automatic HTML encoding will occur when references are interpolated inside strings in the same manner
as when they are written to the template output. At present there is no way to treat this case specially and you will need to make sure that
any data used as part of interpolation is being treated correctly by the encoder.
String parameters
Occasionally you may have some code in your velocity template that makes a call back to some Java logic. To make sure that the value is
being protected by the Anti-XSS mechanism, you must have the string evaluate within the velocity template. If not, you are passing a
reference into the Java code which will not be protected.
You should write the velocity template like this:
$object.doSomething("$action.getValue()")
The quotes around the $action.getValue() call mean velocity will evaluate it before passing it into object.doSomething() and have
a chance to be automatically encoded before being passed to the Java method.
Accessing action context values
Templates rendered from a Webwork Velocity result are able to access values on Webwork's action stack as if they were entries in the
Velocity context. If these values are sourced from getter methods on the current action the automatic encoding system cannot detect whether
the getter method has been marked as HtmlSafe. In this situation the value will be automatically encoded when rendered regardless of any
annotation or method naming convention used by the source of the value.
To remedy this either use the HtmlSafe reference naming convention (e.g. assigning the action context value to a context variable ending
with Html before rendering) or retrieve the value directly from the current action via the $action reference.
Unexpected context types
Some Java code may use the Velocity context as a data passing mechanism to collect information from a template after it is rendered.
public class DataHolder {
@HtmlSafe
public String getHtml() {
return "<strong>My html</strong>";
}
}
myTemplate
#set ($result = data.getHtml())
...
Template myTemplate = getTemplate();
Context myContext = new VelocityContext();
myContext.put("data", new DataHolder());
renderTemplate(myTemplate, myContext);
String message = (String) myContext.get("result");
The above Java code will fail with a ClassCastException at runtime because the reference $result will not be an instance of String
but an instance of BoxedValue due to the way that Confluence's Velocity runtime handles HtmlSafe values in the Velocity context. If there
is demand it is feasible for type compatibility to be restored in this situation via the use of a transparent, unboxing context layer but in general
this mechanism of information passing is discouraged. Context values that are not set from HtmlSafe sources are not affected in this
situation.
How does HTML encoding work?
For this mode of behaviour, there are two parts of the system:
1. A mechanism for marking data as being safe for HTML rendering.
2. A mechanism for encoding any data not marked as safe as it is being written to the output.
A note on naming
You may notice that the #htmlSafe() velocity directive (which causes a template to opt in to Anti-XSS protection) has the opposite
meaning to the @HTMLSafe Java annotation (which causes a Java method to opt out of Anti-XSS protection). We regret this confusing
naming and hope to fix it in a future release. We will, however, ensure that #htmlSafe() continues to work.
Related topics
Anti-XSS documentation
Advanced HTML encoding
Preventing XSS issues with macros in Confluence 4.0
REV 400 Anti-XSS documentation
This page is a draft in progress and visible to atlassian-staff only.
In Confluence 4.0, Anti-XSS encoding for plugins is enforced by default. This is a new setting for this release.
This documentation is aimed at developers. It covers instructions to get Velocity template rendering to function correctly with the new
Anti-XSS mechanism, as some Confluence 3.x plugins may have their content double encoded.
On this page:
What is Anti XSS mode?
Automatic reference encoding in Velocity templates
States of XSS Protection
Explicity opting in to automatic HTML encoding for plugins
Opting out of automatic HTML encoding for plugins
What is Anti XSS mode?
This mode will engage certain behaviours in Confluence intended to reduce the incidence of cross site scripting (XSS) vulnerabilities. At
present this mode enables an automatic data encoding strategy designed to reduce XSS exploits arising from the incorrect encoding of data
embedded in HTML templates. This mechanism does not encode HTML output that plugins generate outside of Velocity templates.
Developers interested in extending this XSS protection feature to their plugins should consult the Enabling XSS Protection
in Plugins document.
Automatic reference encoding in Velocity templates
Many of the past XSS vulnerabilities in Confluence have arisen simply because data from untrusted sources have not been encoded
correctly when mixed with other HTML in Velocity templates. Such encoding failures lead to possible HTML injection attacks that can range
from breaking page rendering, to hijacking user sessions. These security vulnerabilities will always be easily introduced when template
authors have to make a conscious decision to specifically encode untrusted data when rendered. Other disadvantages of this opt-in security
include a proliferation of noise in templates related directly to encoding operations ($generalUtil.htmlEncode et al) and a general
obscuration of where data are being written unsafely to the client. Confluence uses a new rendering mode where all data is HTML encoded
by default unless steps are taken explicitly to avoid this behaviour in templates.
States of XSS Protection
The table below explains how to apply XSS protection and how your plugin will behave.
Protection
state
Effect of protection
Activation mechanism
Anti-XSS
protection for
plugins.
HTML encoding of Velocity templates is enforced for plugins.
Confluence Admin setting 'Enforce
Anti-XSS for plugins' in the '
Security Configuration' screen
(default is on).
Plugin opts in.
Plugin chooses
to enforce
Anti-XSS
Keeps Anti-XSS encoding of plugin template's output even if Confluence
administrator turns off Anti-XSS protection for plugins.
See Enabling XSS Protection in
Plugins.
Plugin opts
out.
Useful when you encounter compatibility issues. Template output is not HTML
encoded. If opting out, your plugin needs to be HTML encoding all the
user-supplied data. It is recommended to update the plugin so that it is compatible
with the new setting.
See Enabling XSS Protection in
Plugins.
Understanding when HTML encoding is applied
The table below shows how and when HTML encoding of templates is applied for plugins.
'Enforce Anti-XSS for plugins' setting
On (default)
Off
Plugin opts in. See Enabling XSS Protection in Plugins.
Plugin opts out. See Enabling XSS Protection in Plugins.
Plugin takes no action.
Key:
= HTML encoding is applied.
= No HTML encoding.
When taking no action, your plugin may no longer work correctly because it encounters double HTML encoding of output. See the
following paragraphs for ways of addressing this. It is recommended to always opt in.
Explicity opting in to automatic HTML encoding for plugins
See Enabling XSS Protection in Plugins.
Opting out of automatic HTML encoding for plugins
See Enabling XSS Protection in Plugins.
Related topics
Advanced HTML encoding
Enabling XSS Protection in Plugins
Preventing XSS issues with macros in Confluence 4.0
Confluence Internals
Confluence is a large and complex application. This area documents some of the more complicated aspects of its design. For a complete
reference, please refer to the source code which is available for download with all commercial licenses.
Bandana Caching
Confluence Bootstrap Process
Confluence Caching Architecture
Confluence Internals History
Confluence Macro Manager
Confluence Permissions Architecture
Confluence Services
Confluence UI architecture
Custom User Directories in Confluence
Date formatting with time zones
HTML to Markup Conversion for the Rich Text Editor
HTTP authentication with Seraph
I18N Architecture
Page Tree API Documentation
Password Hash Algorithm
Persistence in Confluence
Spring IoC in Confluence
Velocity Template Overview
Bandana Caching
This is a technical description of Confluence's Bandana caching mechanism. It is primarily designed for Confluence developers, but
published here because it might prove useful to some plugin developers.
For an overview of all of Confluence's persistence mechanisms, see Persistence in Confluence.
Confluence's Bandana subsystem is used for persisting configuration settings for Confluence and its plugins. Any persistence mechanism
requires careful thought with regard to updates. Transactions are the main mechanism for controlled updates to shared data, and it's
important that transactions are treated consistently across all the subsystems involved.
Confluence 2.3 has moved Bandana data to the database in order for it to be shared among clustered nodes. Using Hibernate meant that the
updates done to the database were immediately transactional, but the Bandana caching layer still needed to be updated to be
transaction-aware.
This document describes the caching system used by Bandana in Confluence 2.3 which allows it to deal correctly with transactional updates.
The caching system may be used more extensively for other areas in Confluence going forward.
Caching layer
The caching layer for Bandana is necessary because all the data is persisted as XML. When configuration objects are retrieved from the data
store, they are deserialized back into Java objects via XStream. This deserialization occurs after the XML has been retrieved by Hibernate,
and is a time-consuming process. Because Bandana objects are used so frequently (at least one per request), a cache of configuration
objects, independent of the Hibernate cache of XML, is required.
The interaction between the key components in the Bandana caching system is shown in the flowchart below.
Bandana caching flowchart
As you can see from the diagram, the CachingBandanaPersister is solely responsible for reading and updating the cache, only delegating
queries to the HibernateBandanaPersister when the required data is not already in the case.
Problems to overcome
Having a cache separate to your transactional data store (Hibernate) presents a few tricky problems:
A cache update is visible to other clients immediately; a database update is only visible to other clients once the transaction
commits.
A cache update can never be rolled back; if the associated database update gets rolled back, the cache is now inconsistent with the
data.
Two concurrent transactions which update multiple caches could interleave their changes, so that neither operation is completed in
its entirety. This is one type of 'lost update' problem.
Read-through cache updates (where a cache is empty and to be populated with data read from the database) should not result in an
inconsistent cache when updates occur concurrently. This is another type of 'lost update' problem and was a serious bug in
Confluence 2.2.
None of these problems is insurmountable, but the solution is fairly complex. The Bandana caching in Confluence 2.3 will have the following
features:
1. Cache updates (except read-throughs) will be enacted on the Coherence cache only after the related database transaction has been
completed successfully.
2. Read-through cache updates will be enacted immediately.
3. All cache updates will use locking when they are processed to prevent lost updates.
4. All cache updates will be visible when reading from the same cache during the same transaction, prior to commit.
These features are provided by a Confluence transactional cache, which is described in detail below.
Transactional cache
The transactional cache makes a best attempt at synchronising the data in the cache and the database when a transaction commits. A
transactional cache consists of two components:
1. Deferred operations cache, which keeps track of update operations to an underlying cache but doesn't actually peform them.
2. Deferred cache transaction synchronization, which performs the deferred updates on the cache once it gets notified of a
successful transaction completion.
These two components collaborate with Spring for transaction management, and the locking and caching subsystems in Confluence.
Confluence Bootstrap Process
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
The start-up (also called "bootstrap") process for Confluence is quite intimidating and can be fragile when changes are made to it. It helps to
have an overview of what is happening across the entire process.
Overview diagram
The diagram below shows an overview of the Confluence start-up process.
Plugin system startup
The plugin system is started last because it might potentially start background threads that are not in the control of Confluence itself. That
means the database and all other configuration should be complete before plugins are started.
Confluence 4.0 brought some simplifying changes to the plugin system start-up process. The old mechanism had a single event,
ConfluenceReadyEvent, published both during setup and during a normal startup to start the plugin system. This event has been deprecated
in 4.0 and no longer influences the startup process. Instead, the plugin system is triggered by:
during setup, once the database schema is created, a DatabaseConfiguredEvent triggers the start of the plugin system
during a normal startup, the PluginFrameworkContextListener starts the plugin system once the upgrade tasks are complete.
This means that during a normal startup, all the relevant lifecycle events are managed by the servlet context listeners: Spring context
starting, upgrade system running, plugin framework starting, lifecycle plugin modules execution.
Confluence Caching Architecture
The cache service provides centralised management of in-memory data caching within the Confluence application. Depending on which
edition of Confluence you are running, the cache service may be backed by ehcache (standard edition) or Oracle Coherence (clustered
edition). Because of this, it is even more important than normal that you only code to the interfaces provided by Confluence, and do not rely
on any of the concrete implementation classes.
For more information about standard and clustered editions of Confluence, please refer to the Coherence license changes document.
The CacheManager
The cacheManager bean in the Confluence Spring application context implements two interfaces: CacheFactory and CacheManager.
The only method on the manager you need to worry about (unless you are maintaining the caching system as a whole) is DOC:
CacheFactory#getCache(java.lang.String name). This method will return a Cache.
To prevent cache name clashes, we suggest using the same reverse domain name syntax for naming caches as you would for Java class
names or plugin keys. You can provide a "friendly name" for the cache management UI by providing an I18N key:
cache.name.[DOC:cache name].
Unflushable Caches
A small number of caches are configured to not be flushed by CacheManager.flushCaches(). These cache names are defined by the
nonFlushableCaches bean in cacheServiceContext.xml. There is currently no plan to broaden this mechanism to allow
plugin-specified caches to opt in to not being flushed.
Differences Between Editions
The differences between the standard and clustered editions of Confluence are:
Standard edition packages the confluence-cache-ehcache module
Clustered edition packages the confluence-cache-coherence module
Both of these modules are contained in the cache subdirectory of the main Confluence source tree.
Having both or neither of confluence-cache-ehcache and confluence-cache-coherence in the Confluence classpath will cause the system not
to run.
Implementation
There are a couple of different places the caching subsystem hooks into the rest of Confluence.
Bootstrapping
During bootstrapping, Confluence will try to load /bootstrapCacheContext.xml into the Spring context. This file will be found in one of
the cache implementation jars. This context file is responsible for providing an implementation of the ClusterManager,
ClusterConfigurationHelper and HibernateCacheFactory.
You can tell which edition of Confluence you are running by calling ClusterManager#isClusterSupported. This will return true in
clustered editions, false otherwise.
Hibernate
Hibernate is configured to use the ConfluenceCacheProvider as its cache provider. This provider delegates to the
HibernateCacheFactory (as defined in the bootstrap context above) to instantiate the correct cache implementation depending on the
Confluence edition being run.
The Cache Manager
During main application startup, Confluence will try to load /cacheProviderContext.xml into the Spring context. This file will also be
found in one of the cache implementation jars and is responsible for instantiating the correct implementation of the CacheManager.
The Cache Management UI
The user interface (and backing implementation) for viewing, flushing and adjusting the sizes of caches are implemented as plugins within
each of the cache implementation jars.
Gotchas
ehcache will log a warning if a cache is created for which it does not have an explicit configuration in ehcache.xml. We should
ensure that there are no such warnings before releasing a new version of Confluence.
Confluence Internals History
A brief history of Confluence noting when major features or internal changes were introduced. See the release notes for full details.
Confluence 2.6
Redesign of recent updates, children web UI. Introduced classic theme which maintains old look and feel.
Source structure changed to fit better with Maven 2.
Confluence 2.5.5
Server ID support.
Confluence 2.5
Pages can be restricted to multiple users and/or groups.
Confluence 2.4
Editable comments.
Bundled plugins shipped in atlassian-bundled-plugins.zip, including Plugin Repository Plugin.
First release built with Maven 2.
Confluence 2.3
Clustering possible with a clustered license.
Changed from EhCache caching layer to Tangosol Coherence. This was for both application caches and Hibernate second-level caches.
Moved Bandana (configuration framework) storage from home directory (/config/) to the BANDANA table in the database.
Confluence 2.2
Structure of attachments folder in home directory changed from /attachments/<filename>/<version> to
/attachments/<id>/<version> to fix DOC:CONF-4860.
Personal spaces.
Simpler Atlassian-User configuration when atlassian-user.xml replaces atlassianUserContext.xml.
Confluence 2.1
Confluence starts using Atlassian-User.
Confluence 2.0
Export Word documents.
Confluence Macro Manager
Available:
Confluence 4.0 and later
On this page:
Introduction
Source of macros
Spring autowiring
Example XHTML macro definition
Example usage from a macro
Introduction
In Confluence 4.0 macros are accessed via a new com.atlassian.confluence.macro.xhtml.MacroManager interface.
public interface MacroManager
{
Macro getMacroByName(String macroName);
void registerMacro(String name, Macro macro);
void unregisterMacro(String name);
LazyReference<Macro> createLazyMacroReference(final ModuleDescriptor<?> moduleDescriptor);
}
The interface contains methods to register and unregister macros. These are called automatically when plugins are installed,
uninstalled, enabled or disabled or individual macros are enabled or disabled.
The main method however is getMacroByName. It obtains an instance of a Macro unless the macro does not exist or is disabled in
which case null is returned.
The Confluence 3.x com.atlassian.renderer.v2.macro.MacroManager interface still exists in order to support 3.x plugins, to migrate content to
the new XHTML storage format and to view content that has not been fully migrated.
Source of macros
The Confluence 4.0 MacroManager is composed of 4 sub managers, one for each source of macros. When requested for a macro, each sub
manager is checked in sequence.
XhtmlMacroManager is populated with Confluence 4.0 style macros. Plugin developers supply 4.0 macro definitions as
<xhtml-macro> elements in atlassin-plugin.xml files. Internally the definitions are held as XhtmlMacroModuleDescriptors.
V2CompatibilityMacroManager is populated with bodyless Confluence 3.x style macros automatically wrapped in a
V2CompatibilityMacro (a 4.0 macro). Plugin developers supply 3.x macro definitions as <macro> elements in atlassin-plugin.xml
files. Internally the definitions are held as CustomMacroModuleDescriptors. Older style macros with bodies are not automatically
wrapped, as the 4.0 macro's body type (PLAIN_TEXT or RICH_TEXT) is unknown.
UserMacroLibraryMacroManager is populated with user macros added via the admin interface. Internally it delegates to a
UserMacroLibrary which keeps track of user macros.
UserMacroPluginMacroManager is populated with user macros added via the plugin subsystem. Plugin developers supply user
macro definitions as <user-macro> elements in atlassin-plugin.xml files. Internally the definitions are held as
UserMacroModuleDescriptors.
Spring autowiring
The Confluence 4.0 MacroManager may be autowired using the name "xhtmlMacroManager"
The Confluence 3.x MacroManager is still available using the name "macroManager".
Example XHTML macro definition
<xhtml-macro name='album' class='com.atlassian.confluence.plugins.macros.albums.macros.AlbumMacro'
key='album'
documentation-url="help.albums.ablum.macro"
icon="/download/resources/albums/icons/album.png">
<description>Album of pages, attachments, blog posts and external pages.</description>
<resource type="velocity" name="help"
location="com/atlassian/confluence/plugins/macros/albums/album-help.vm">
<param name="help-section" value="confluence"/>
</resource>
<category name="formatting"/>
<parameters>
<parameter name="views" type="string" required="true" default="default"/>
</parameters>
</xhtml-macro>
Example usage from a macro
The following (rather contrived) example is taken from a macro that outputs only selected nested macros. The output from nested macros are
only included if there is a parameter with the nested macro's name. The value of the parameter is supplied to the nested macro. Note the
check that the macro returned from the macroManager is not null. It may be null if the macro is disabled or does not exist.
public class TestMacro implements Macro
{
private final XhtmlContent xhtmlContent;
private final MacroManager macroManager;
public TestMacro(XhtmlContent xhtmlContent, MacroManager macroManager)
{
this.xhtmlContent = xhtmlContent;
this.macroManager = macroManager;
}
public String execute(final Map<String, String> parameters, String body, final
ConversionContext conversionContext) throws MacroExecutionException
{
body = getStorageBody(parameters, conversionContext);
final StringBuilder stringBuilder = new StringBuilder("");
final AtomicReference<MacroExecutionException> nestedMacroExecutionException = new
AtomicReference<MacroExecutionException>();
try
{
xhtmlContent.handleMacroDefinitions(body, conversionContext, new
MacroDefinitionHandler()
{
public void handle(MacroDefinition macroDefinition)
{
String testValue = parameters.get(macroDefinition.getName());
if (testValue != null && testValue.length() > 0)
{
Macro macro = macroManager.getMacroByName(macroDefinition.getName());
if (macro != null)
{
try
{
stringBuilder.append(macro.execute(Collections.<String,String>emptyMap(), testValue,
conversionContext));
}
catch (MacroExecutionException e)
{
nestedMacroExecutionException.set(e);
}
}
}
}
});
if (nestedMacroExecutionException.get() != null)
{
throw nestedMacroExecutionException.get();
}
return stringBuilder.toString();
}
catch (XhtmlException e)
{
throw new MacroExecutionException(e);
}
}
// ...
Confluence Permissions Architecture
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
Permissions checking overview
In Confluence, a permission check is a question like does user U have permission to do action A to content C? The way we answer that
question deserves a brief overview of the logical operations:
1. First, Confluence checks that the user is allowed to access the application. This involves user and group checks for user U against
the defined global permissions.
2. Second, Confluence checks space permissions. This involves user and group checks for user U against the space permissions for
2.
action A for the space containing content C.
3. Finally, Confluence checks content level restrictions like page permissions. This involves user and group checks for user U against
the content level permissions for action A on content C.
4. If all three checks succeed, user U is permitted to do action A to content C by Confluence.
The logical operations involved in a "user and group check for user U" look like this, taking space permissions as an example:
1.
2.
3.
4.
First, Confluence retrieves all the space permissions for the space containing content C from the database.
Next, it gets the groups that user U is a member of and checks if each of the group has permission required to do action A.
Next, it checks whether user U is one of the individual users that has been granted the permissions required to do action A.
If the membership status of user U and the group isn't cached already, Confluence determines which user repository (database,
LDAP, Crowd) owns the group. Confluence checks in the user repository whether user U is a member of the group, and caches the
result for subsequent checks.
5. If either check succeeds – that is, if either user U or one of the her groups has permission for the action – user U is permitted to do
action A to content C by Confluence.
The API used for performing all these checks is described in more detail below.
The PermissionManager API
The core API for checking permissions in Confluence is through the PermissionManager (javadoc). The two most important methods on
this interface are:
hasPermission – does user U have permission P on object O?
hasCreatePermission – does user U have permission to create object of type T inside container C?
So, for example. If you have a page, and want to determine if a user is able to edit it:
boolean canEdit = permissionManager.hasPermission(user, Permission.EDIT, page);
Or, if you want to know if user is permitted to comment on a page:
boolean canComment = permissionManager.hasCreatePermission(user, page, Comment.class);
Permissions are defined as constants on the Permission interface (javadoc). They are VIEW, EDIT, EXPORT, REMOVE,
SET_PERMISSIONS and ADMINISTER.
If the supplied user is null, the anonymous permission is checked
For the purpose of checking create permissions, the "containing" object is not the same as the parent. You test if a page can be
created in a space, and a comment within a page, not within its parent page or comment.
There is a special object – PermissionManager.TARGET_APPLICATION – that represents Confluence itself and is used for
checking global permissions
Some permission checks don't make sense, for example checking if you can REMOVE TARGET_APPLICATION, or checking if you
can administer a page. Checking a nonsensical permission will result in an IllegalStateException
Similarly, if you check permissions against a type of object that the PermissionManager doesn't know how to check permissions
against (i.e. it doesn't have a delegate for that class, see below), it will throw an IllegalArgumentException.
Permission Inheritance
The system does not cater for any inheritance of permissions. having Permission.ADMINISTER against an object does not imply that you
also have Permission.EDIT.
However, certain permissions are considered "guard permissions". For example, permission to VIEW TARGET_APPLICATION is required to
do anything in Confluence (it's generally referred to as "Use Confluence" permission). Similarly, permission to VIEW a particular space is
required to do anything else in that space. If you are modifying Confluence permissions through the UI, removing a guard permission from a
user or group will also remove any dependent permissions that user/group might have. If you are modifying Confluence permissions
programatically, you are responsible for making sure they end up in a sensible state w.r.t guard permissions.
PermissionManager Quirks
The PermissionManager always checks to ensure a user is not deactivated, and that a user has the "Use Confluence" guard
permission.
The PermissionManager does not check if the user is a member of the super-user confluence-administrators group. If you
want super-users to override your permission check, you have to do it manually.
PermissionManager Implementation
For every type of target object (or container in the case of create permissions) there is a corresponding PermissionDelegate (javadoc)
that performs the actual checks. The code should be reasonably self-explanatory
Shortcuts
Getting all viewable/editable spaces for a user
Finding all spaces for which the user has a particular permission is a common, and reasonably expensive operation in instances with large
numbers of spaces. For this reason we have a number of shortcut methods on SpaceManager that go straight to the database:
getPermittedSpaces – get all spaces for which a user has VIEW permission
getPermittedSpacesByType – get all spaces of a certain SpaceType for which the user has VIEW permission
getSpacesEditableByUser – get all spaces in which the user can create or edit pages
getEditableSpacesByType – get all spaces of a certain SpaceType in which the user can create or edit pages
Note: These operations are still not cheap, especially in situations where the user being checked may be a member of a large number of
groups.
Searching / Lucene
The Lucene index contains enough information for searches to determine if particular results are visible to the user performing the search. So
long as you're not going direct to the Lucene index yourself, and use one of Confluence's search APIs to find content, the content returned
should not require any more tests for VIEW permission.
Checking Permissions from Velocity
It might be difficult (or even impossible) to construct a required PermissionManager call from velocity code, especially for calls to the
hasCreatePermission() method. For this reason there is an object called permissionHelper (javadoc) in the default velocity context
with a number of helper methods to perform common permission checks.
If you can not find an appropriate method on the PermissionHelper, your best course of action is to write a [Velocity Context Plugin] to
encapsulate your permission checking code (or if you're an Atlassian developer, obviously, just add it to the helper).
Other Permission-related APIs
PermissionCheckDispatcher
The PermissionCheckDispatcher allows you to check if a particular user has access to a certain Confluence URL. It will only work if the
target of the URL is a WebWork action (it works by instantiating the action referred to by that URL, filling in all the relevant form values, and
calling isPermitted on the action).
The PermissionCheckDispatcher used to be the preferred way of testing whether or not to display a link in the web UI. However, its use
is being phased out because it can be very slow. Do not use the PermissionCheckDispatcher for new code. Instead, use the
PermissionManager directly. If you are in UI code, use the PermissionHelper (javadoc), a convenience class that is placed in the
Velocity context to make permission checks more Velocity-friendly.
SpacePermissionManager
The SpacePermissionManager is a low-level API for directly manipulating user permissions. You should not use the
SpacePermissionManager for checking permissions, because it tightly couples your permission check to the internal representation of
permissions in the database. Use the PermissionManager for all permission checks.
The SpacePermissionManager should only be used:
By a PermissionDelegate to translate between a logical permission check, and the back-end implementation of that permission
By permissions management code (i.e. granting, revoking or displaying user permissions)
Adding New Permissionable Objects
To make it possible to use a new type of object as the subject of a permissions check, you will need to:
1. write a PermissionDelegate for that object's class.
2. instantiate the delegate in Spring (delegates are defined in securityContext.xml)
3. add that object to DefaultPermissionManager's delegates property in securityContext.xml
Adding New Permissions
1. Ask if this permission is really necessary? For example a lot of things that look like they should be permissions are really "create"
permissions (like "can comment on page" is really "can create (comment, page)"
2. Add a new method to the PermissionDelegate interface.
3. For each existing PermissionDelegate, implement your new method. Throw IllegalStateException if the permission is not
relevant to that delegate's object
4. Add a new constant to the Permission interface to represent your permission (see the existing examples)
To Do
Permissions checking on labels (add, remove) are broken, and still being done via page permission checks
We should probably throw UnsupportedOperationException for bogus checks instead of IllegalStateException
Currently create permissions are tested against container, not parent. a hasCreatePermission(user, container, parent,
klass) could be useful
Confluence Services
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
Here's a quick overview of some of the services defined in Confluence (for more details of what a service is, see the High Level Architecture
Overview).
Current Services
Database Service
This service is defined in databaseSubsystemContext.xml and productionDatabaseContext.xml. It provides database
connectivity and Hibernate ORM to the application.
The reason for splitting the service into two files is to allow for easier testing. productionDatabaseContext.xml extracts the database
configuration from the bootstrap configuration, and brings up Confluence with the Tangosol Coherence clustered cache. If you substitute that
one file with testDatabaseContext.xml you will instead get a pre-configured in-memory HSQL database and in-memory caching.
Because configuring Hibernate dynamically is non-trivial, the database service is unavoidably dependent on every class we want to persist
via Hibernate. You can see this in the list of .hbm.xml files loaded in databaseSubsystemContext.xml.
Bandana Service
Provides a generic configuration/preferences storing service using XStream to serialize POJO configuration objects to XML. Confluence's
bandana service persists to the database.
Cache Service
Provides centralised management of in-memory cached data. The backing cache implementation is provided by ehcache in the standard
edition of Confluence, and Oracle Coherence in the clustered edition. For more information about the cache service, see Confluence Caching
Architecture.
For more information about standard and clustered editions of Confluence, please refer to the Coherence license changes document.
Event Service
Provides a simple service for producing and consuming events. Defined in eventServiceContext.xml. Confluence's event service is
cluster-aware, distinguishing between events that are limited to a single node of the cluster, and events that must be broadcast to every
node.
Plugin Service
Provides the Atlassian plugin framework, in pluginServiceContext.xml. Confluence's plugin service is customised to deal with bundled
plugins (plugins that are provided with the application but that may be upgraded by the end user), and to behave mostly sanely in a cluster.
The plugin system hasn't been entirely service-ised yet, as all the different plugin module loaders result in dependencies back to whatever
subsystem they're being plugged into.
Task Queue Service
A central manager for queues in Confluence. I'm not entirely sure this should exist as it currently adds no value whatsoever beyond being a
lookup mechanism, which Spring does already.
Not Services
Things that should be services, but aren't.
Quartz Scheduling
Pretty obvious next candidate for servicization, but possibly tricky because the Spring/Quartz integration might not be very friendly.
Backup/Restore
Something to keep in mind if we clean up the backup/restore code
User Management
I wasn't going to mess with user-management while there was a different atlassian-user task in the release pipeline.
Wiki Rendering
This seems like a reasonably trivial candidate to convert to a service. There's only one dependency on non-service code (the image renderer
depends on the attachment manager).
Mail (sending and receiving)
The sending and receiving of email is currently a mess of singleton configurations, clients sticking mail jobs directly on the queue, and very
little going through Spring at all. This should be fixed.
External Network Access
It would be nice to have Confluence provide a service for accessing the outside world so we can throttle number of connections, provide
central configuration of time-outs and authentication, and so on.
Image Manipulation
Right now we have a thumbnail manager that lives with attachments, but it would be nice to make this more generic, and at least support
multiple thumbnail sizes.
Confluence UI architecture
Rendering frameworks
There are two frameworks that do the template rendering in Confluence: Webwork and Sitemesh. The confusing bit is that both of them use
Velocity as their templating engine. We try to distinguish them by using *.vm for templates processed by Webwork, and *.vmd for those
processed by Sitemesh.
Rendering contexts
There are four different Velocity contexts used in Confluence:
templates processed by Webwork use the context defined in ConfluenceVelocityContext
templates processed by Sitemesh as a result of the #applyDecorator() directive use the context defined in
ApplyDecoratorDirective
templates processed by Sitemesh as a result of the URL mapping in decorators.xml use the context defined in ProfilingPageFilter
templates processed by the notification queue use the context defined in VelocityRenderedQueueItem.
The two Sitemesh contexts are pretty much the same, but the Webwork velocity context contains a lot more stuff than either of the Sitemesh
ones.
Logical structure
The following diagram shows the logical structure of the Confluence UI.
Confluence UI Architecture - Logical Structure
Rendering pipeline
The following diagram shows the flow of control through the Confluence UI.
Confluence UI Architecture - Execution Flow
In more detail, the flow of control goes:
Webwork gets request, maps request URL to action using xwork.xml
Webwork maps response of action to a Velocity template using xwork.xml
Webwork launches Velocity handler on template (*.vm) with context defined in ConfluenceVelocityContext
Velocity process content in *.vm file
Within an #applyDecorator() directive:
Velocity calls the ApplyDecoratorDirective class with the parameters and body content of the directive
Any #decoratorParam() directives are processed by the ParamDirective class, which pushes bits of the current Velocity
context into the ApplyDecoratorDirective parameters
ApplyDecoratorDirective matches the name parameter of the directive with a *.vmd file from decorators.xml
ApplyDecoratorDirective launches Sitemesh on a decorator template (*.vmd) with context defined in
ApplyDecoratorDirective
Sitemesh returns decorated content
Velocity template finished processing rest of *.vm file, returns to Webwork
Web.xml servlet filter 'sitemesh' maps to ProfilingPageFilter, a Sitemesh page filter
Sitemesh uses the request URL mapping in decorators.xml to launch a decorator template (*.vmd) with context defined in
ProfilingPageFilter
Sitemesh returns decorated content as response.
You can find out which beans are in which context by looking in the classes above. A full list would be too long to include here. Note that
even though the ApplyDecoratorDirective launches a Sitemesh decorator template, the Sitemesh template doesn't get automatic access to
the Velocity context. The only bits that are passed through are done with the #decoratorParam() directive.
Wow, pretty complicated. But it lets us do cool stuff like implement custom themes, apply layouts and more.
Sample page
Below is a sample decorated page with the templates responsible for the rendering indicated.
Decorated page
Custom User Directories in Confluence
Atlassian does not support code-level customisations of our products. We will support your instance for problems other
than user-management-related issues if you are using custom user management code.
This documentation applies to Confluence 3.5 and later.
Introduction
Writing a custom user directory should be the last resort of customers who cannot use any of our supported user management
configurations, and cannot use the database or remote API to manually synchronise their users with Confluence.
These instructions are written for customers who are able to write and debug Java code in a web application like Confluence. If you do not
have experience doing this, custom user directories are probably not the best approach for your situation.
Writing a custom user directory
To use a custom user directory in Confluence, you need to write a custom subclass of
com.atlassian.crowd.directory.RemoteDirectory. You may wish to subclass one of the existing implementations in Crowd:
MicrosoftActiveDirectory.
If you extend an existing implementation, an instance of your directory will be wrapped in a database-caching layer (
DbCachingRemoteDirectory) like the default implementations are. If you don't extend an existing implementation, you will not get any
caching. This should be considered when evaluating the performance of your custom user directory.
Installing a custom user directory
To use a custom directory, you need to configure a directory in Confluence through the UI of the appropriate type, then modify the database
to set the implementation class to the type you've created.
1. Install the compiled custom user directory in Confluence's classpath – either as a JAR in confluence/WEB-INF/lib/ or as a
class file in the appropriate directory under confluence/WEB-INF/classes/
2. Using the Confluence web UI, set up one of the built-in directory types with the configuration options you need.
3. Update the database to set 'impl_class' (and 'lower_impl_class') in the cwd_directory table in the database to the fully qualified
name (and lowercase fully-qualified name) of your RemoteDirectory implementation class.
4. Add any additional attributes required by your implementation to cwd_directory_attribute table.
Note: code customisations are not supported by Atlassian. If you need help with implementing this, you can try the forums or other
communication options available in the Atlassian Developer Network.
Related pages
Configuring User Directories
Creating a Custom Directory Connector (Crowd-specific documentation)
Date formatting with time zones
Introduction
Confluence 2.3 supports a time zone preference for a user. This means all dates in the system must be formatted using the same process to
appear in the user's time zone correctly. This document describes how dates are formatted in Confluence. It may be useful to plugin
developers who need to format dates in a special way inside Confluence.
DateFormatter
The new class introduced in Confluence 2.3, DateFormatter, allows formatting in the user's timezone. See the full javadoc for details, but
methods include:
String format(Date date) – Formats the date and returns it as a string, using the date formatting pattern.
String formatDateTime(Date date) – Formats the date and returns it as a string, using the date-time formatting pattern.
String formatServerDate(Date date) – Same as format(Date), but doesn't perform time zone conversion.
Most methods format the time in the user's time zone. The 'server' methods format the time in the server's time zone.
Accessing the DateFormatter in Velocity
In Velocity, using the DateFormatter is easy because it is in the Velocity context. In a normal Velocity template (*.vm), such as an action
result, you might use it like this:
$dateFormatter.format($action.myBirthdayDate)
If you want to use the DateFormatter in a Velocity decorator (*.vmd), such as a custom layout or theme, you need to access it via its getter on
the action:
$action.dateFormatter.format( $page.lastModificationDate )
Accessing the DateFormatter in code
The DateFormatter is constructed by the ConfluenceUserPreferences object, which can be obtained from the UserAccessor. The code below
gives a demonstration:
ConfluenceUserPreferences preferences = userAccessor.getConfluenceUserPreferences(user);
DateFormatter dateFormatter = preferences.getDateFormatter(formatSettingsManager);
System.out.println(dateFormatter.formatDateTime(date));
The userAccessor and formatSettingsManager are Spring beans which can be injected into your object. You can usually get the user
from the context of your macro or plugin, or using AuthenticatedUserThreadLocal.getUser().
HTML to Markup Conversion for the Rich Text Editor
Introduction
Classes and Responsibilities
DefaultConfluenceWysiwygConverter
DefaultWysiwygConverter
WysiwygNodeConverter
Styles
ListContext
WysiwygLinkHelper
Overview of the HTML to Markup Conversion Process
Preprocessing the HTML
Converting the Document Fragment to Markup
Post-processing the markup
Worthwhile Style Improvements
Rendering in 'For Wysiwyg' Mode
How To Fix Bugs
Writing Tests
Finding Problems
Introduction
This component enables the rich Text Editor by converting HTML (created by the renderer, then edited by the user) into Confluence Wiki
Markup.
It works like this:
1. Submit HTML to WysiwygConverter.convertXHtmlToWikiMarkup
2. ...
3. Get Wiki Markup back.
This document explains step 2 in some more detail. Most problems with this stage stem from difficulty in determining the correct amount of
whitespace to put between two pieces of markup.
Classes and Responsibilities
This section briefly describes the main classes involved and their responsibilities.
DefaultConfluenceWysiwygConverter
Converts Wiki Markup to HTML to be given to the rich text editor, and converts edited HTML back to markup. Creates RenderContexts from
pages and delegates the conversion operations to a WysiwygConverter instance.
DefaultWysiwygConverter
Converts Wiki Markup to XHTML to be given to the rich text editor, and converts edited XHTML back to markup. This class contains the guts
of the HTML -> Markup conversion, and delegates the Markup -> HTML conversion to a WikiStyleRenderer, with the
setRenderingForWysiwyg flag set to true in the RenderContext.
WysiwygNodeConverter
Interface for any class which can convert an HTML DOM tree into Markup. Can be implemented to convert particular macros back into
markup. The macro class must implement WysiwygNodeConverter and give the macro's outer DIV a 'wysiwyg' attribute with the value
'macro:<macroname>'.
Styles
Aggregates text styles as we traverse the HTML DOM tree. Immutable. Responsible for interpreting Node attributes as styles and decorating
markup text with style and colour macros/markup.
ListContext
Keeps track of nested lists – the depth and the type.
WysiwygLinkHelper
Just a place to put some static methods for creating HTML attributes describing links, and for converting link HTML nodes into markup.
Overview of the HTML to Markup Conversion Process
Preprocessing the HTML
1. First the incoming HTML is stripped of newlines and 'thinspaces', which were inserted during the rendering process so that there
were places to put the cursor to insert text.
2. XML processing instructions (which can be present when HTML is pasted from MS Word) are stripped.
3. NekoHTML is used to parse the HTML into an XML document fragment.
Converting the Document Fragment to Markup
This uses the convertNode method, which has the honour of being the longest method in Atlassian (although not the most complex by
cyclomatic complexity measures).
The signature of this method is:
String convertNode(
Node node,
Node previousSibling,
Styles styles,
ListContext listContext,
boolean inTable,
boolean inListItem,
boolean ignoreText,
boolean escapeWikiMarkup)
That is, the method returns the markup needed to represent the HTML contained in the DOM tree, based on the current context (what styles
have been applied by parent nodes, are we already in a table or a list and so on).
The body of this method is a large case statement based on the type of the current node and the current state. The typical case gets the
markup produced by its children, using the convertChildren method, decorates it in some way and returns the resulting string.
The convertChildren method simply iterates over a node's children calling convertNode and concatenating the markup returned.
In order to determine how much white space separates the markup produced by two sibling nodes we often need to know the type of each
node. That is why convertNode takes a previousSibling argument. The getSep method takes the two nodes to be separated and
some state information. t uses a lookup table to decide what type of whitespace (or other text) to use.
Post-processing the markup
1. Clean up whitespace and multiple newlines – the conversion process may insert too many newlines or multiple "TEXTSEP" strings
to separate text – these are collapsed into single newlines and single spaces.
2. Replace {*} style markup with simply * where possible.
Worthwhile Style Improvements
1. Split up convertNode so that it is responsible for deciding what treatment the current node needs, and then calling
convertTextNode, convertDivNode etc.
2. Put the state passed to convertNode into an immutable object to reduce the parameter clutter. Don't use a Map.
3. Refactor WysiwygLinkHelper – it's very confusing.
Rendering in 'For Wysiwyg' Mode
The HTML produced by the renderer to be displayed by the Rich Text editor is not identical to that generated for display. It contains extra
attributes which are cues to the conversion process. The following list isn't exhaustive, but gives the flavour of the types of considerations
involved.
1. Some errors should be rendered differently so that the original markup isn't lost – e.g. an embedded image which can't be found
should be displayed as a placeholder, not just an error message.
2. When links are rendered extra attributes are added to the tag so that the appropriate alias, destination and tooltip can be
determined. See WysiwygLinkHelper's javadoc for details.
3. Some errors put the erroneous markup in a span with the "wikisrc" class, which causes its contents to be directly used as markup.
4. This speaks for itself:
// @HACK
// The newline before the title parameter below fixes CONF-4562. I have absolutely no idea
HOW it fixes
// CONF-4562, but the simple fact that it does fix the problem indicates that I could spend
my whole life
// trying to work out why and be none the wiser. I suggest you don't think too hard about it
either, and
// instead contemplate the many joys that can be found in life -- the sunlight reflecting
off Sydney
// Harbour; walking through the Blue Mountains on a dew-laden Autumn morning; the love of a
beautiful
// woman -- this should in some way distract you from the insane ugliness of the code I am
about to check
// in.
//
// Oh, and whatever you do, don't remove the damn newline.
//
// -- Charles, November 09, 2005
if (renderContext.isRenderingForWysiwyg())
buffer.append("\n");
5. Thin spaces are added at strategic points so that there is somewhere to place the cursor when inserting text, e.g. at the end of the
page, in a new paragraph.
6. Curly brackets are treated differently: a '{' typed in the RTE is interpreted as the start of a macro tag, not as an escaped '{' – you
6.
must explicitly escape '{ and '}' in the RTE.
7. Macros.
From a wysiwyg point of view there are four cases:
a. Macros with unrendered bodies (or no bodies). These appear as {macro} ... unrendered body ... {macro}, so the user can
edit the body text in wysiwyg mode.
b. Macros with rendered bodies, but which the editor doesn't 'understand' – that is, the editor can't manipulate the HTML
produced by the macro. These are rendered as {macro} ... rendered body ... {macro}. A macro indicates that the editor
doesn't understand it by returning true from suppressMacroRenderingDuringWysiwyg(). Most macros should do this, unless
the Wysiwyg converter understands how to create a new instance of the macro. The user can edit the HTML in the body of
these macros, which will be converted back to markup.
c. Macros we fully understand. These are simply rendered as normal (but surrounded by a div or span describing them).
These return false from suppressMacroRenderingDuringWysiwyg().
d. Macros which are responsible for their own rendering. These return true from
suppressSurroundingTagDuringWysiwygRendering()
8. The bq. markup adds an attribute to the tag to distinguish it from a blockquote tag produced by the {quote} macro.
9. The header DIV of panel macros is given a wysiwyg="ignore" attribute, because it is generated from the macro parameters. This
means that is you edit the title of a panel macro in the RTE the change is ignored.
10. Look at the InlineHtmlMacro for an example of a macro which implements WysiwygNodeConverter.
How To Fix Bugs
Writing Tests
The first thing to do is to write a failing test. At the moment all the tests are in com.atlassian.renderer.wysiwyg.TestSimpleMarkup
. Keeping them al together is reasonable, as they run quickly and you will want to make sure that your fixes don't break any of the other tests.
There are two types of test – markup tests and XHTML tests.
Use a markup test when you have a piece of markup which doesn't 'round trip' correctly. For instance, perhaps the markup:
* foo
* bar
becomes
* foo
* bar
when you go from wiki markup mode to rich text mode and back again.
The body of the test you write would be:
testMarkup("* foo\n\n* bar");
which will check that the markup is the same after a round trip. Note that it is OK for markup to change in some circumstances – two different
markup strings may be equivalent, and the round trip will convert the starting markup to 'canonical markup' which renders identically to the
initial markup. There are also pathological cases where a round trip may switch markup between two equivalent strings – these should be
fixed, even though they don't break the rendering as they show up as changes in the version history.
If a bug is caused by the conversion of user-edited (or pasted) HTML into markup.
In this case you write a test like this:
testXHTML("...offending HTML...", "...desired markup...")
This test first checks that the desired markup round-trips correctly, then that the HTML converts to that markup.
Finding Problems
Once you have written your test you need to find out what the converter is doing.
Running the test in debug mode and putting breakpoints in testMarkup/testXHTML is the best way of doing this. As you track down the
nodes causing problems you can put breakpoints in the part of convertNode which handles the offending type of node.
You can also set 'debug' to true in DefaultWysiwygConverter.java:44 – this will dump the XHTML produced by Neko, turn off the
post-processing mentioned above, and print out details of the separator calculations in the generated markup string.
So you might see:
[li-li
false,false]
which means that two list items, not in a table and not in a (nested) list get separated by a newline. You can tweak the table of separators as
needed.
HTTP authentication with Seraph
Introduction
This document describes how the default security system in Confluence works, using the Seraph library for HTTP authentication.
Extending the security system by subclassing Seraph's authenticator and configuring the seraph-config.xml file is outside the scope of
this document. See Single Sign-on Integration with JIRA and Confluence.
Flowchart diagrams
The easiest way to understand Confluence's authentication process is with the following diagrams.
Authentication flowchart
Because the Authenticator.login(request, response, username, password, rememberMe) method occurs three times, and
is slightly complex, it has been broken into its own sub-flowchart.
Login method flowchart
Supported authentication methods
The default Seraph authenticator supports four methods of authentication, as can be seen in the flowchart:
request parameters: os_username and os_password
session attribute storing the logged-in user
cookie storing username and password ('remember me' login)
HTTP basic authentication via standard headers.
Each method is tried in the order above. A successful login at an earlier method continues without checking the later methods. Failure at one
method means continuing with the later methods until all are exhausted. At this point, the user is considered an anonymous user, and treated
according to the permissions of an anonymous user in Confluence.
Looking through the source code will show that Seraph supports role-based authentication, but this is only used in Confluence for the
/admin/ URL restriction.
Related pages
Understanding User Management in Confluence
Confluence Internals
Single Sign-on Integration with JIRA and Confluence.
I18N Architecture
This document sheds some light on how Confluence looks up a message text from one of the resource bundles.
Loading of Resources
Currently the only implementation of the I18NBean interface is the DefaultI18NBean. When it is instantiated, it attempts to load resources
from the following locations:
1. Load /com/atlassian/confluence/core/ConfluenceActionSupport.properties and
/external-links.properties and create a CombinedResourceBundle containing both of them.
2. Load all language pack resources which match the current user's locale, which would be:
/com/atlassian/confluence/core/ConfluenceActionSupport_<lang_country_variant>.properties
/com/atlassian/confluence/core/ConfluenceActionSupport_<lang_country>.properties
/com/atlassian/confluence/core/ConfluenceActionSupport_<lang>.properties
Warning
The files are only loaded if a language pack for the specific locale is installed.
3. Load all resources of type 'i18n' which match the current user's locale:
<resource location>_<lang_country_variant>.properties
<resource location>_<lang_country>.properties
<resource location>_<lang>.properties
<resource location>.properties
Resource Bundle Structure
DefaultI18NBean internally creates a list of CombinedResourceBundles per locale, combining resource bundles from language packs
and i18n resources of the same locale. When you call one of the DefaultI18NBean.getText() methods it will go through the bundles in
the following order:
1.
2.
3.
4.
lang_country_variant
lang_country
lang
default
On a lookup of a combined resource bundle, the last occurrence of a given key takes precedence during the lookup, which results in the
following lookup order:
1. i18n resource
2. language pack
The order within i18n resources and language packs with the same locale is not defined, as they are loaded from the plugins which are
loaded in an arbitrary order. This is not an issue in most cases, as you usually have no overlapping keys between your resources anyway.
Example
Given the following situation:
The current user's locale is 'de_DE_foo'
There are language packs installed for the locales 'de_DE_foo', 'de_DE' and 'de'
There is one resource of type 'i18n' available from one of the plugins. The location for that resource is 'com.example.resources'
The resource bundle structure would look like this:
Lookups will always happen from top to bottom until a message for a given key is found.
RELATED TOPICS
Confluence Internals
Page Tree API Documentation
This documentation is aimed at developers needing to include page-tree (page reordering) functionality in their Confluence or Plugin code.
Let's start with an example - editing a page deep inside the Confluence Doc space.
This tree is generated by the following markup in
listpages-dirview.vm :
#requireResource("confluence.web.resources:jquery")
#requireResource("confluence.web.resources:page-ordering-tree")
<div id="tree-div"></div>
and the following JavaScript :
var expandedNodes;
#if ($openNode)
expandedNodes = [
{pageId: "$openId"}
#foreach ($nodeId in $openedNodes)
,{pageId: "$nodeId"}
#end
];
#end
jQuery(function ($) {
tree = $("#tree-div").tree(
{
url: contextPath + '/pages/children.action',
initUrl: contextPath + '/pages/children.action?spaceKey=$space.key&node=root',
parameters: ["pageId"],
append: function() {
recordMove(this.source.pageId, this.target.pageId, "append");
},
insertabove: function() {
recordMove(this.source.pageId, this.target.pageId, "above");
},
insertbelow: function() {
recordMove(this.source.pageId, this.target.pageId, "below");
},
onready: function () {
if (typeof expandedNodes != "undefined") {
var doHighlight = function() {
tree.findNodeBy("pageId", "$openId").highlight()
};
tree.expandPath.apply(tree, expandedNodes.reverse().concat(doHighlight));
}
}
}
);
## Callbacks when append/insert events are fired by the tree.
var recordMove = function (sourceId, targetId, position) {
$ .ajax({
url: contextPath + "/pages/movepage.action",
data: {pageId: sourceId, point: position, targetId: targetId},
complete: function(xmlhttp) {
var resultsDiv = document.getElementById("resultsDiv");
resultsDiv.innerHTML = xmlhttp.responseText;
if (xmlhttp.getResponseHeader("success") != "true") {
tree = tree.reload();
}
if ( position == "append") {
tree.findNodeBy("pageId", targetId).reload();
}
}
});
}
});
Once you understand the above code you'll have a good overview of how the tree works.
Stepping through the code
To start, "expandedNodes" is simply a JS array of objects with "pageId" variables. The pageIds are populated using Velocity but any method
is okay.
Next, "jQuery(function ($) {" is just a way of enabling $ to be used to gain access to a jQuery object. The page tree code has been written as
an extension of the jQuery object, so we call $("#tree-div") to get a jQuery object wrapping the div with id "tree-div" that we added to our
HTML markup.
Creating the tree
When the tree() function is called, an object with options is passed. We'll work through each of the options in turn:
url
This is the location that the tree will load its nodes from as the user navigates it. The alternative is to directly include the tree data in the
HTML in nested <ul> or <ol> format.
initurl
(optional) This is the location that the tree will load its "trunk" (initial nodes) from. If not specified, the "url" will be used and the server would
be expected to return something useful. If specified, it can (as in this case) pass extra information to the same server address.
parameters
(optional but important) This array specifies the key/value pairs that will be sent to the url when making node requests. The nodes returned
from the server will be expected to include key/value pairs for each of the parameters in the array, which are stored in the tree internals and
sent with any future requests from that node.
append, insertabove, insertbelow
(optional) These options specify callback functions that should be executed when their respective event occurs:
append - means that a tree node (i.e. a page) has been moved inside another node
insertabove - means that a tree node has been moved above another node
insertbelow - means that a tree node has been moved below another node
For each of these events the most important data is source and target. Source is the node that is being moved and target is the "other" node
that the source is interacting with.
While these three events are the most common, you can also hook callbacks to :
grab - when the user clicks and holds on a node
drag - when the user moves the mouse while a node is grabbed
drop - when the user releases the mouse button
load - when node data is returned from the server
nodeover - when a node is dragged over another node
nodeout - when a node is dragged out of a node it was previously over
onready - covered next
onready
(optional) Called when the tree has finished loading, from either its first initUrl call or from hard-coded list data. In this case, if
"expandedNodes" exist the tree should be expanded to show them. The way that this is done is worth explaining in more detail.
Finding and Expanding Nodes
Once the first level of tree data has been loaded into the browser, the next step is often to drill into the tree to expose a particular element.
This is done by calling :
tree.expandPath(expandedNodes, callback)
Internally, this function works recursively through the array of expandedNodes, locating the node in the loaded tree and, if present, opening it.
In the screenshot above, this is equivalent to passing an array of nodes:
"Confluence Documentation Home", "Confluence Development Hub", "Confluence Architecture", "Confluence Internals". Note that the nodes
are referenced by pageId and must be in the correct order - each node to expand must already be loaded. Once each node is expanded the
callback function (if present) is executed. In this example, the callback highlights a node inside the expanded nodes - note that the "Bandana
caching" node is only loaded from the server when the "Confluence Internals" node is expanded, so the highlighting must occur after this.
Individual nodes are located with this syntax:
tree.findNodeBy(attribute-name, attribute-value)
Usually the attribute-name will be one of the parameters in the options originally used to create the tree; in this case it is "pageId".
The object returned by findNodeBy has a number of functions that can be called on it :
open(callback) - expand this node. If the node has not been opened yet and the tree has a url, child-node JSONs will be requested
from the server and appended to the node.
close() - closes an opened node
getAttribute(attrName) - returns the attribute value for the given name (eg "pageId")
setAttribute(attrName, attrValue) - sets an attribute
highlight() - adds "highlighted" class to the node
makeDraggable() - allows the node to be moved in the tree
makeUndraggable() - stops the node from being moved (e.g. when moving a page while editing, other nodes in the tree cannot be
moved)
setText(text) - updates the node text
append(node) - appends a node to this node
below(node) - places the passed node after this node
above(node) - places the passed node before this node
remove() - removes this node from the tree
reload() - if this node has children, reload them from the server
Other tree functions
In addition to the functions covered in the example above, the tree object exposes the following variables and functions:
options - the options passed in the original tree() function call
reload() - clears and rebuilds the tree
append(node) - appends a node to the tree root
Password Hash Algorithm
Confluence uses an algorithm to hash local users' passwords. The result for the password 'admin' is:
x61Ey612Kl2gpFL56FT9weDnpSo4AV8j8+qx2AuTHdRyY036xxzTTrw10Wq3+4qQyB+XURPWx1ONxp3Y3pB37A==
The encryption algorithm is based on BouncyCastle's SHA1-512 implementation. You can see one version of the source code for it here. The
entire Confluence source code is available here.
If you'd like to try to import users from a different user management system into a local instance of Confluence, you're likely to be better off
using a different solution than re-hashing existing passwords. Some options would be:
1. Use Crowd, which is extendable and offers connectors to user repositories.
2. Import users using their plain text passwords, leveraging the Confluence XML-RPC and SOAP APIs. One good client is the
Confluence Command Line Interface.
Persistence in Confluence
Available:
Confluence 2.2 and later
Changed:
In Confluence 3.3 and later, plugins can define their own contexts using the KeyedBandanaContext interface.
There are three main persistence APIs which are used in Confluence. Each of these APIs is discussed on this page:
1. Bandana – XML persistence, easy to use in plugins.
2. Hibernate – database persistence, difficult to extend.
3. Content properties – database persistence for properties associated with a piece of Confluence content.
Because Bandana is the primary persistence API used by plugin developers, it will be covered in more detail than the other two APIs.
Bandana
Bandana is an Atlassian framework for persistence of arbitrary Java objects. The concepts used in Bandana are very simple:
Bandana stores data in contexts. Confluence defines one global context and one context per space. The relevant class is
ConfluenceBandanaContext. In Confluence 3.3 and later, plugins can define their own contexts using the KeyedBandanaContext
interface.
Each context stores key-value pairs. The key is a String and the value can be any Object. It should typically implement
Serializable. If the key or value types are defined within a plugin, the class should have a no-argument constructor to avoid class
loading issues.
Based on this design, the BandanaManager has methods for storing and retrieving values from a context by key:
void setValue(BandanaContext context, String key, Object value) – store a value against a key in the Bandana
context.
Object getValue(BandanaContext context, String key) – get a key's value from the Bandana context. Returns null if
no matching context and key exists.
void removeValue(BandanaContext context, String key) – remove a key and value from the Bandana context.
(Available in Confluence 3.3 and later.)
Object getValue(BandanaContext context, String key, boolean lookUp) – same as above, except if lookUp is
true and the context is a space context, this method will also check the global context if no matching key is found in the space
context.
Iterable<String> getKeys(BandanaContext context) – provides an iterable to allow enumeration of all keys within a
context. (Available in Confluence 3.3 and later.)
For plugins which use a context not provided by the application, we recommend that you use a context for your Bandana values that includes
the full package name of your plugin. For example, a theme plugin might use a context like
org.acme.confluence.mytheme.importantPreference.
Serialization
By default, Bandana uses XStream to convert objects into XML for storage. It is however possible to provide your own method of
serialisation. If your BandanaContext implements the BandanaSerializerFactory interface (available in Confluence 3.3 and later) it will be
used to create an serialiser to serialise and deserialise your objects.
Data storage
Prior to Confluence 2.3, this XML was written to the filesystem in the Confluence home directory. The file
config/confluence-global.bandana.xml stores the global context, and there is a file config/spaceKey
/confluence-space.bandana.xml with the configuration for each space. In Confluence 2.3 and later, Bandana data is written to the
BANDANA table in the database, with three columns for context, key and a serialized value.
Getting access to BandanaManager
To get access to the BandanaManager from your plugin code, normally you only need to include a private BandanaManager field with an
associated constructor parameter. Spring will construct your object and pass in the required component.
public class MyMacro extends BaseMacro {
private BandanaManager bandanaManager;
// constructor called by Spring
public MyMacro(BandanaManager bandanaManager) {
this.bandanaManager = bandanaManager;
}
// main method of macro
public String execute(...) {
// do stuff with bandanaManager
return "...";
}
}
Hibernate
Confluence uses the open source persistence framework Hibernate. Confluence 2.2.x uses Hibernate version 2.1.8.
Each object to be persisted has a *.hbm.xml file which sits in the same directory as the associated class in the Confluence web application.
For example, Label.class has an associated Label.hbm.xml which describes how label objects will be persisted. The particular details
vary from class to class, but typically include:
the database table used to hold the data (Confluence bootstrap creates these tables if they do not exist)
the column names and mappings to class attributes
any special queries used for functionality in Confluence (for example, to retrieve a list of personal labels)
All this data is expressed in the standard Hibernate mapping format. In some cases, there is a single mapping file for all subclasses of a
particular class. For example, ContentEntityObject.hbm.xml includes mappings for pages, news, mail and space descriptions.
The Hibernate mapping files are listed in mappingResources bean in applicationContext.xml. The list of Hibernate types (and
mapping files) cannot be extended dynamically by plugins.
Although it might be possible to extend Confluence's database through Hibernate, this is not recommended. There are a few downfalls with
extending our Hibernate configuration:
1. You need to maintain your forked copy of the Hibernate mappings file against each new version of Confluence.
2. Your new Hibernate objects will not be protected from (or necessarily upgraded to) any changes we make in the schema in future
versions.
3. Unless you really understand our code, something weird will happen.
Avoid using Confluence's database to store custom data – use content properties or Bandana instead.
Content properties
Another form of persistence, content properties are key-value pairs associated with a ContentEntityObject and stored in the database.
Content properties are accessed through the ContentPropertyManager like this:
Page page = pageManager.getPage("KEY", "Page title"); // retrieve the page however you like
// retrieving a String property - use your plugin key in the property key
String favouriteColor = contentPropertyManager.getStringProperty(page,
"com.example.plugin.key.favourite-colour");
// storing a String property
contentPropertyManager.setStringProperty(page, "com.example.plugin.key.favourite-colour",
"purple");
You should get the ContentPropertyManager and PageManager injected into your macro, servlet, etc. using the techniques outlined on
Accessing Confluence Components from Plugin Modules (also demonstrated in the section above).
Spring IoC in Confluence
On this page
On this page
Introduction
Spring contexts
Bean declarations
Autowiring
Plugin dependency injection
Accessing Spring beans directly
Transaction proxy beans
Introduction
The Spring Framework provides an inversion of control (IoC) container that Confluence uses for managing objects at runtime. This document
provides an overview of how this relates to Confluence, specifically focused at the needs of plugin developers and those extending
Confluence.
If you're looking for the quick overview on how to access Confluence managers from your plugin, check out Accessing Confluence
Components from Plugin Modules.
The purpose of an IoC container is to manage dependencies between objects. When you go to use an object in Confluence it will have all its
dependencies ready and available to use. For example, calling a method on a PageManager will typically require a PageDao to work
correctly. Spring ensures that these dependencies are available when they are needed, with a little bit of guidance from us.
Spring contexts
Confluence uses a number of Spring contexts to separate our objects into discrete subsystems. The contexts are declared as servlet context
parameters in confluence/WEB-INF/web.xml. The snippet below shows the Spring contexts listed in web.xml for Confluence 2.3:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath:/applicationContext.xml,
classpath:/securityContext.xml,
classpath:/databaseSubsystemContext.xml,
classpath:/indexingSubsystemContext.xml,
classpath:/eventSubsystemContext.xml,
classpath:/rpcSubsystemContext.xml,
classpath:/upgradeSubsystemContext.xml,
classpath:/wikiSubsystemContext.xml,
classpath:/wikiFiltersSubsystemContext.xml,
classpath:/importExportSubsystemContext.xml,
classpath:/schedulingSubsystemContext.xml,
classpath:/pluginSubsystemContext.xml,
classpath:/atlassianUserContext.xml
</param-value>
</context-param>
What this means is there are 13 context XML files in the Confluence classpath which specify the objects in Confluence which are managed
by Spring. When I say 'in the Confluence classpath', in practice I mean they live in confluence/WEB-INF/classes/. The biggest and
most important is applicationContext.xml, which we'll have a look at now.
Bean declarations
Around line 100 in the Confluence 2.3 applicationContext.xml, you'll find the schemaHelper bean as a good example:
<bean id="schemaHelper" class="bucket.core.persistence.hibernate.schema.SchemaHelper">
<property name="mappingResources">
<ref local="mappingResources"/>
</property>
<property name="hibernateConfig">
<ref bean="hibernateConfig"/>
</property>
</bean>
The bean has an ID for Spring to reference it ('schemaHelper'), a class name which will be used to automatically create the bean
('bucket.core.persistence.hibernate.schema.SchemaHelper'), and a number of properties. In this case, the properties are references to other
beans in the current context, mappingResources and hibernateConfig.
Because we use the setter injection method in Confluence, this declaration means two things about the SchemaHelper Java class:
it must have a public no-args constructor
it must have two public methods: setMappingResources() and setHibernateConfig(). Both these must take one argument
which is an interface implemented by the appropriate bean.
Other than these two requirements, the SchemaHelper class can be any normal Java class. It can have other constructors, other public
methods, and can implement or extend any interface or class that you like.
The purpose of registering a bean in Spring is two-fold:
1. When you access the SchemaHelper bean through Spring, it will have its mappingResources and hibernateConfig dependencies
injected before you use it.
2. You use the bean as a dependency elsewhere, to automatically get it injected into your own class (more on this below).
Only Confluence beans are registered in Spring via an XML context. Spring Component Plugins are registered at runtime
when the plugin is enabled. Other plugin classes such as actions are autowired without registration with Spring.
Autowiring
In the bean declaration for schemaHelper bean above, each property has the same name as the Spring bean which is used to satisfy it. For
example, the 'mappingResources' property uses the mappingResources bean, which is set by the setMappingResources() method on
the schemaHelper. Spring provides a shortcut for leaving these declarations out, called autowiring.
For example, the declaration for themeManager bean is marked as autowire 'byName' (near line 1000):
<bean id="themeManager" class="com.atlassian.confluence.themes.DefaultThemeManager"
autowire="byName" />
Looking at the DefaultThemeManager class, we see it has four setter methods:
1.
2.
3.
4.
public void setBandanaManager(BandanaManager)
public void setEventManager(EventManager)
public void setGlobalTheme(String)
public void setPluginManager(PluginManager)
Spring looks at the names of the four methods, tries to find beans with IDs of 'bandanaManager', 'eventManager', 'globalTheme', and
'pluginManager'. If they exist, it calls the setter method with the relevant bean as an argument.
In this case, methods 1, 2 and 4 will be called by Spring to inject dependencies. Method 3 (setGlobalTheme) is just a setter used for
something else, not called by Spring. This is the drawback of autowiring: it is slow and can waste time trying to find dependencies uselessly.
Using autowiring reduces the need for writing a lot of XML, and also provides a method of dependency injection for objects which aren't
registered in the Spring context XML files like plugin modules.
Plugin dependency injection
Almost all Confluence plugin types are autowired. What this means, is if your macro plugin needs to access a Confluence page, it can simply
do so like this:
public class MyMacro extends BaseMacro
{
private PageManager pageManager;
public String execute(Map parameters, String body, RenderContext renderContext)
{
// ...
Page page = pageManager.getPage(spaceKey, pageTitle);
// ...
}
// ... implement other methods ...
/**
* Called by Spring to inject pageManager
*/
public void setPageManager(PageManager pageManager)
{
this.pageManager = pageManager;
}
}
Autowired components must use the interfaces used by the manager to work with different versions of Confluence. The implementing class
used for various managers may change over time, but the bean ID and interface will be preserved.
Internally, the way the components are autowired is via Confluence's ContainerManager. You can also do this with your own objects if
required:
ContainerManager.autowireComponent(object);
Accessing Spring beans directly
If you need access to Confluence managers or other Spring beans without autowiring your class, you can use the ContainerManager directly.
For example, to get the pageManager bean:
PageManager pageManager = (PageManager) ContainerManager.getComponent("pageManager");
You should always use autowiring in preference to this method because it makes your code easier to change and easier to test. Inside
Confluence this method is sometimes required to break circular dependencies.
Transaction proxy beans
Confluence uses Spring's transaction handling by wrapping some objects in transaction proxy beans.
Velocity Template Overview
Velocity is a server-side template language used by Confluence to render page content. Velocity allows Java objects to be called alongside
standard HTML. Users who are writing Writing User Macros or plugins (or customised PDF exports in Confluence 2.10.x and earlier versions)
may need to modify Velocity content. General information is available from the Velocity user guide.
Useful Resources
No content found for label(s) velocity-related.
Basic Introduction to Velocity
Example Usage
A variable in velocity looks like this:
$foo
To set a variable:
#set ($message = "Hello")
A basic if statement:
#if ($message == "Hello")
Message received and is "Hello"
#end
A velocity variable which evaluates to null will simply render as the variable name. See the Velocity User's Guide
Related Content
No content found for label(s) velocity-related.
Confluence Objects Accessible From Velocity
Confluence has a few distinct Velocity contexts for different purposes in the application (user macros, email templates, exports), but the most
commonly used context is called the "default context".
Velocity usage guidelines for plugins
To allow deprecation and code change breakages to be detected at compile time, it is recommended that where possible you add
functionality which calls Confluence code in your plugin Java code (i.e. actions or components) rather than in a Velocity template. You can
call any method on your plugin action from Velocity with $action.getComplicatedCustomObject() instead of putting complicated logic
in your Velocity template that is not checked by the compiler.
For example, if your plugin needs a calculate list of particular pages to display in the Velocity template, you should do the following:
inject a PageManager into your action class by adding a setPageManager() method and pageManager field (more information
on dependency injection)
in your action's execute() method, retrieve the desired pages using the pageManager object and store them in a field in your
class called calculatedPages
add a method to your action, getCalculatedPages(), which returns the list of pages
in your Velocity template, use $action.calculatedPages to get the calculated pages from the action and display them.
Although it is supported at the moment, you should not be performing data updates directly from Velocity code and future versions of
Confluence may prevent you doing this in your plugin.
Default Velocity context
This list highlights the most important entries in the default Velocity context. The full list is defined in Confluence's source code in
velocityContext.xml. The default Velocity context is used for templates rendered by:
Confluence WebWork actions
macros which call MacroUtils.defaultVelocityContext()
mail notifications (with additions – see below)
user macros (with additions – see below).
Variable
Description
Class Reference
$action
The current WebWork action
Your action class, normally a
subclass of
ConfluenceActionSupport
$i18n
$i18n.getText() should be used for plugin internationalisation.
I18NBean
$dateFormatter
Provides a date and time formatter suitable for the exporting user's locale and
environment.
DateFormatter
$req
The current servlet request object (if available)
HttpServletRequest
$req.contextPath
The current context path. Used for creating relative URLs: <a
href="$req.contextPath/dashboard.action">Dashboard</a>.
String
$baseUrl
The base URL of the Confluence installation. Used for creating absolute URLs
in email and RSS: <a href="$baseUrl/dashboard.action">Back to
Confluence</a>.
String
$res
The current servlet response object (should not be accessed in Velocity)
HttpServletResponse
$settingsManager
Can retrieve the current global settings with
$settingsManager.getGlobalSettings()
SettingsManager
$generalUtil
A GeneralUtil object, with useful utility methods for URL encoding etc
GeneralUtil
$action.remoteUser
, $remoteUser
The currently logged in user, or null if anonymous user.
User
$userAccessor
For retrieving users, groups and checking membership
UserAccessor
$permissionHelper
Can be used to check permissions, but it is recommended that you check
permission in your action
PermissionHelper
$attachmentManager
Retrieving attachments
AttachmentManager
$spaceManager
Space operations
SpaceManager
User macro Velocity context
User macros have a Velocity context which includes all the above items and some additional entries specific to user macros. See Guide to
User Macro Templates for a list of the latter.
Email notification Velocity context
If customising the Velocity templates for Confluence's email notifications, the following items are available in addition to the default context
above.
Variable
Description
Class Reference
$stylesheet
Default stylesheet CSS contents
String
$contextPath
Same as $req.contextPath in the default context
String
$subject
The email notification subject
String
$wikiStyleRenderer
Wiki rendering support
WikiStyleRenderer
$renderContext
Notification render context for use with $wikiStyleRenderer
RenderContext
$report
Daily report (only for digest notifications)
ChangeDigestReport
$showDiffs
Whether this notification should include diffs
boolean
$showFullContent
Whether this notification should include full page content
boolean
$diffRenderer
Diff rendering support
StaticHtmlChangeChunkRenderer
$diff
Diff for the notification, if enabled
ConfluenceDiff
Export Velocity context
The export context does not include any of the values from the default context. See Available Velocity Context Objects in Exporters for a
complete list.
Related Pages
Accessing Confluence Components from Plugin Modules
XWork-WebWork Module
Velocity Context Module
XWork Plugin Complex Parameters and Security
Writing Confluence Plugins
Rendering Velocity templates in a macro
When writing a macro plugin , it's a common requirement to render a Velocity file included with your plugin. The Velocity file should be
rendered with some data provided by your plugin.
The easiest way to render Velocity templates from within a macro is to use VelocityUtils.getRenderedTemplate and simply return
the result as the macro output. You can use it like this:
public String execute(Map params, String body, RenderContext renderContext) throws MacroException
{
// do something with params ...
Map context = MacroUtils.defaultVelocityContext();
context.put("page", page);
context.put("labels", labels);
return
VelocityUtils.getRenderedTemplate("com/atlassian/confluence/example/sample-velocity.vm", context);
}
RELATED TOPICS
Macro Module
Confluence UI Guidelines
These are Atlassian's internal guidelines, published for the reference of plugin developers. More thorough documentation can be found in the
Plugin development guide. Not all of these guidelines are followed throughout Confluence yet, but they set the direction of our future work in
the product's front-end.
Separation of content, presentation and behaviour
It is imperative that functionality in Confluence separate content, presentation and behaviour for maintainable front end code. This means the
following:
HTML content goes in Velocity files. No CSS or JavaScript goes in Velocity files. Not even in style or onclick attributes.
CSS styles go in CSS files.
JavaScript code goes in JS files. JS files must be static and not generated by Velocity or any other mechanism.
The remainder of this document describes how to achieve this in Confluence.
Naming Conventions
At the moment we have two simple naming rules:
use dashes for HTML element ids or class names e.g. comment-actions
use camel cases for variables, method names in javascript e.g. commentToggle()
Markup
Please note as of Confluence 3.5, the DOCTYPE will change to HTML5.
Markup must be valid HTML 4 Strict and follow the Atlassian HTML coding conventions.
Use meaningful tags in your markup and do not use markup for non-semantic formatting (eg. only use <strong> for text which should have
strong emphasis) or layout (that means no <table>s for layout!).
For example, the following markup is suitable for the File menu in an application:
<h3 class="menu-title">File</h3>
<ul class="navigation menu">
<li id="menu-item-new-window"><a href="#">New Window</a> <span class="shortcut">N</span></li>
<li id="menu-item-new-tab"><a href="#">New Tab</a> <span class="shortcut">T</span></li>
...
</ul>
The use of meaningful tags, rather than a sea of tables and divs make it rendering the page without stylesheets possible, and provide better
degradation in mobile browsers, Internet Explorer, and so on.
Assign classes and IDs that allow you to style the content appropriately. Try to place classes and IDs "higher up" the DOM to enable efficient
styling. In most cases, there will be a container element that can be used for this purpose.
Bad:
<div class="funky">
<p class="my-funky-style">Foo.</p>
<p class="my-funky-style">Bar.</p>
<p class="my-funky-style">Sin.</p>
<p class="my-funky-style">Qua.</p>
</div>
.my-funky-style { ... }
Good:
<div class="funky">
<p>Foo.</p>
<p>Bar.</p>
<p>Sin.</p>
<p>Qua.</p>
</div>
.funky p { ... }
Use multiple classes in markup when required, but be aware that IE6 has limitations with parsing style selectors including multiple classes.
Ensure IDs are unique within the page or Javascript code will not be able to access the elements properly.
Do not use inline script and style tags. Put them in separate CSS and JS files and use #requireResource - see DOC:Including web
resources below.
Put attribute values in double-quotes, use lower-case tags and attribute names in all cases. Do not use self-closing tags (e.g. <link />) or
include tags or attributes that are not part of the spec. Use closing tags for all elements that support them. We want our HTML to be valid and
well laid out (don't guess, use the validator!).
Although HTML 4 allows the omission of attribute values for boolean-style attributes, do not omit attribute values in Confluence HTML.
Rather, set the attribute value to the name of the attribute. For example: <input type="checkbox" name="subscribe"
checked="checked">. This allows better forwards-compatibility with XHTML/HTML5.
Be sure that you are familiar with how Confluence automatically HTML encodes template references.
Including Web Resources
Confluence web resources (css & javascript) are now all defined in a System Web Resources plugin under
confluence/src/etc/java/plugins/web-resouces.xml. You should no longer use the #includeJavascript macro that
generates inline script tags wherever you invoke it. You should now use #requireResource(pluginKey:webResourceKey) to tell the
WebResourceManager that you require a particular resource on this page. Note, this macro does not generate the actual markup but is
done in conf-webapp/src/main/webapp/decorators/includes/header.vm via $webResourceManager.getResources().
Example from wiki-textarea.vm
#requireResource("confluence.web.resources:prototype")
#requireResource("confluence.web.resources:scriptaculous")
#requireResource("confluence.web.resources:yui-core")
#requireResource("confluence.web.resources:ajs")
#requireResource("confluence.web.resources:dwr")
#requireResource("confluence.web.resources:page-editor")
Currently, we don't have a way to indicate dependencies between the web resources. For example, it would be nice to define in the web
resource module 'ajs web depends on yui-core'. The work around at the moment is to explicitly make calls to #requireResource in the
order you would like the resources to be included. Multiple calls to the same web resource does not result in multiple includes of that
resource, but rather the WebResourceManager will try to maintain the 'order' in which the resources were called. This is why you may see
duplicate chunks of #requireResource scattered throughout Confluence.
For more information about the declaration and inclusion of web resources, see: Including Javascript and CSS resources.
Stylesheets
No more site-css.vm
We no longer have the huge site-css.vm velocity template. This has been split up into separate css files:
master.css, master-ie.css, wiki-content.css and more (see master-styles web resource module)
default-theme.css
colors-css.vm
The only dynamic styles in Confluence are the colors set by colour schemes, hence all color styling was extracted into colors-css.vm.
We also have a separate stylesheet for the setup wizard, setup.css.
Stylesheet ordering
CSS resources are included in the following order:
1. Confluence default styles (resources included via calls to #requireResource such as master.css)
2. Colour scheme styles
3. Theme styles
Colour scheme and theme styles are also included in the header via the combined.css action call. It essentially produces a set of imports
to other css resources, hence the name 'combined'.
Sample output of combined.css
@import "/s/1317/7/1/_/styles/colors.css?spaceKey=DOC";
@import
"/s/1317/7/1.0/_/download/resources/com.atlassian.confluence.themes.default:styles/default-theme.css";
Note, the old monster main-action.css (i.e.StylesheetAction) has now been deprecated and split into separate actions.
Style guidelines
Use the shortest form wherever possible. That means using three character colours, combined margin declarations, simple selectors, and so
on:
.menu li.menu-item a {
color: #fff;
background: #8ad6e8;
margin: 0;
line-height: 1.2;
padding: 5px 1em;
}
Avoid child selectors like ul > li. Instead use a class name of "first" for compatibility with IE. (You can use child selectors if you want to
intentionally exclude IE, however.)
When designing a new section of Confluence, consider using a style reset to clear out default styles for lists, paragraps and so on.
Internet Explorer stylesheets
Very often, it's desirable to serve custom styles to Internet Explorer. Confluence web resources can be marked as 'ieOnly' in order to be
rendered in conditional comments only parsed by IE.
See Including Javascript and CSS resources for more details and an example of how to do this.
Print stylesheets
Stylesheet web resources can be include a 'media' parameter with a value of 'print' to have media='print' included on their <link> tag so
they are only used for printing. See the master-styles web resource in Confluence's web-resources.xml for an example. Any media type will
be passed directly into the link tag, so you can also provide styles for handheld, projector media types, etc. as supported by your user's
browsers.
See Including Javascript and CSS resources for more details and an example of how to do this.
JavaScript
JavaScript guidelines
Use closures to prevent unnecessary variables and functions being exposed in the global scope. For example, the code below binds an
onclick handler to a button in the page without exposing itself or its variables in the global scope:
jQuery(function ($) { // I am a closure, hear me roar!
var i = 0;
function increment() { alert(i++); }
$("button.counter").click(increment);
});
Don't introduce new global variables in JS. Rather put them under some namespace such as AJS.Editor.MyGlobalVariable.
Don't mix Velocity and Javascript code. See DOC:Passing dynamic values to Javascript if you need to pass dynamic values to Javascript.
Atlassian.js (AJS)
To avoid depending on a particular JavaScript library too much, we have atlassian.js ("AJS") as an abstraction on top of each particular
library's functions. In Confluence 2.9, AJS wraps jQuery so many functions work in the same style as that library. Throughout Confluence's
JavaScript, we should use AJS to make common function calls such as $, toggleClassName etc. wherever possible. This enables us to
easily change the underlying JavaScript library later on if necessary.
Event Handlers
In the past, we have used embedded event handling like (horribly) so:
Sample code from common-choosetheme.vm
<tr bgcolor="ffffff" onMouseOver="style.backgroundColor='f0f0f0'"
onMouseOut="style.backgroundColor='ffffff'"
onclick="javascript:checkRadioButton('themeKey.default');">
We are now moving to binding event handlers in javascript, using the jquery's bind function.
Sample code from page-editor.js
AJS.toInit(function () {
AJS.$("#markupTextarea").bind("click", function () {
storeCaret(this);
});
AJS.$("#markupTextarea").bind("select", function () {
storeCaret(this);
storeTextareaBits();
});
AJS.$("#markupTextarea").bind("keyup", function () {
storeCaret(this);
contentChangeHandler();
});
AJS.$("#markupTextarea").bind("change", function () {
contentChangeHandler();
});
AJS.$("submit-buttons").bind("click", function (e) {
AJS.Editor.contentFormSubmit(e);
});
});
You may have noticed in the above example, all the binds are wrapped in a AJS.toInit() function. This is only necessary if you require
the code to be fired after DOMReady.
Unfortunately, we haven't been able to port all the embedded event handler code to javascript. If you encounter such code during
development, please fix it up as you go
Using jQuery directly
For advanced dynamic functionality, you can use jQuery directly. However, we don't allow jQuery to set the global $ variable (which is still
used by Prototype.js), so you should use the 'jQuery' global variable as shown below.
To use jQuery properly in a JS file, do the following:
jQuery(function ($) {
// your code goes here
// use '$' for jQuery calls
});
i18n (Only 4.0+)
To get translated strings in your javascript, use the following syntax:
var label = AJS.I18n.getText("some.key");
var labelWithArgs = AJS.I18n.getText("some.other.key", "arg1", "arg2");
Then add the following transformation xml to you web resource definition in your atlassian-plugin.xml.
<web-resource key="whats-new-resources" name="What's New Web Resources">
<transformation extension="js">
<transformer key="jsI18n"/>
</transformation>
....
</web-resource>
Passing dynamic values to Javascript (before 4.0)
We are now trying remove inline scripts that are scattered throughout Confluence. Most of these inline scripts are in the velocity templates so
dynamic values such as i18n strings and values from actions can be used in the script. We now have a way around this via AJS.params. You
simply need to define a fieldset in your template with classes "hidden" and "parameters". AJS will automatically populate itself with the
inputs defined in the fieldset.
Example from page-location-form.vm
<fieldset class="hidden parameters">
<input type="hidden" id="editLabel" value="$action.getText('edit.name')">
<input type="hidden" id="doneLabel" value="$action.getText('done.name')">
<input type="hidden" id="showLocation" value="$action.locationShowing">
<input type="hidden" id="hasChildren" value="$!helper.action.page.hasChildren()">
<input type="hidden" id="availableSpacesSize" value="$action.availableSpaces.size()">
<input type="hidden" id="spaceKey" value="$action.space.key">
<input type="hidden" id="pageId" value="$pageId">
<input type="hidden" id="actionMode" value="$mode">
<input type="hidden" id="parentPageId" value="$!parentPage.id">
</fieldset>
With the above code, you would use the above i18n edit label by calling AJS.params.editLabel in your javascript.
If your i18n message includes variables in the form {0}, {1}, etc. which are meant to be populated by JavaScript values, you can use the
AJS.format() function to present them. For example:
$(".draftStatus").html(
AJS.format(AJS.params.draftSavedMessage, time) // "Draft saved at {0}"
);
The second and subsequent arguments to AJS.format replace all instances of {0}, {1}, etc. in the first argument, which must be a String.
Passing dynamic values to Javascript (Only 4.0+)
We have revised the way we do this in Confluence 4.0 with the use of meta tags. A velocity macro #putMetadata is available for your
convenience to output the meta tags in the right format. Note that any duplicate calls to put metadata for the same key will be overridden.
#putMetadata('page-id', $page.id)
This produces the following markup in the head element of the page.
<meta name="ajs-page-id" content="590712">
To access this data in javascript you can use the AJS.Meta.get api:
var pageId = AJS.Meta.get("page-id");
To view all the currently available meta tags on a page, see AJS.Meta.getAllAsMap().
Templates (Only 4.0+)
In Confluence 4.0, you now have a convenient way to use a template in JavaScript using soy. More details are document on this page.
Related pages
Including Javascript and CSS resources
Web UI Modules
Confluence Plugin Guide
Anti-XSS documentation
W3C HTML 4.0 specification
Templating in JavaScript with Soy
This is only available in Confluence 4.0+.
This page describes how you can write a Soy template in your plugin to use in JavaScript. This can be useful when generating a dialog or
some piece of content to display on the page.
References
Soy Commands
Soy Concepts
Quick Guide
1. Create a soy file
Under the plugins resource directory, create a soy file. Each soy file needs to have a namespace declaration at the top of the file. It must be
declared before any templates are declared. You can think of the namespace as a javascript namespace equivalent. You can put it under
your plugin's namespace or use the existing Confluence.Templates.
2. Write your template
Next comes your actual template declaration, which is simply declared with {template .templateName}. If your template needs parameters,
you must declare them in the 'javadoc' part before your template. You can then use the param in the template with a {$paramName} syntax.
Here is a simple example soy file:
example.soy
{namespace Confluence.Templates.Example}
/**
* Renders a Hello message.
* @param name the name of the user
*/
{template .helloWorld}
<div>Hello {$name}!</div>
{/template}
3. Add your template to a web-resource
Since a soy file gets transformed in a js file, we need to declare it with all the other js files in a <web-resource> element of a plugin xml. You
will also need to copy the soy transformer config into the <web-resource> element. Here is an example:
<web-resource key="example-resources" name="Example Resources">
...
<transformation extension="soy">
<transformer key="soyTransformer"/>
</transformation>
...
<resource type="download" name="example-soy.js" location="/soy/example.soy"/>
....
</web-resource>
4. Use the template in your javascript
You can now invoke the template by simply calling a javascript function with your previously declared namespace. In this example it would be
something like:
// display a hello message on the page
var template = Confluence.Templates.Example.helloWorld({name: "John Smith"});
AJS.messages.info(jQuery("body"), {body: template, closeable: true});
i18n in your templates
You will often need to use i18n in your templates. To do so, simply use the {getText('key')} syntax.
GOTCHA: You must use single quotes for string literals in soy. Double quotes are for html attribute values.
Here is an example template using i18n.
/**
* A template for a dialog help link
* @param href key of the doc link url
*/
{template .helpLink}
<div class="dialog-help-link">
<a href="{$href}" target="_blank">{getText('help.name')}</a>
</div>
{/template}
Calling another template from a template
You may want to reuse an existing template from another template. To do this you can simply use the call command to invoke another
template. You can optionally pass in all the params from one template to another or configure individual parameters that get passed through.
Here is an example with a single parameter being passed through.
/**
* Move page dialog help link
*/
{template .helpLink}
{call Confluence.Templates.Dialog.helpLink}
{param href: docLink('help.moving.page') /}
{/call}
{/template}
Deprecation Guidelines
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
Because Confluence doesn't have an official API yet, you should assume that any change you make to manager interfaces, model objects,
services, or really any commonly used code will in some way impact third-party plugin developers. As such, we should always be careful to
deprecate, rather than remove old functionality.
Deprecation
All deprecated methods in Confluence MUST include, as the first thing after the @deprecated tag, the text "since n",
where n is the version number at which the tag was added, followed by either a short explanation of why the method should
not be used, or a direction to use a different method.
Rationale
Because we want to keep third-party developers happy, we should deprecate methods that may be used by plugins instead of just deleting
them. However, deprecated methods pollute the namespace, and keeping them around indefinitely just encourages people to continue to
use them.
Therefore, we should record when a method has been deprecated, and before each major release we should remove anything that has
stayed deprecated for more than six months or two major versions (whichever is longer).
Examples
For a simple redirect, the deprecation tag is the only Javadoc the method should require. Developers should consult the doc for the linked
alternative to find out more about what the method is likely to do:
/** @deprecated since 2.3 use {@link Space#isValidSpaceKey} */
boolean isValidSpaceKey(String key);
For a "this is no longer the right way to do things" deprecation, a longer explanation may be required, and the old Javadoc may need to be
retained for developers who are still stuck doing things the old way for some reason. A short explanation is put in the deprecated tag itself,
and the detail is put in the main body of the Javadoc:
/**
* Return all the content a user has authored in this space.
*
* <b>Warning:</b> This method has been deprecated since Confluence 2.1 because it is
* insanely inefficient to do in the database. You should migrate your code to use the
* SmartListManager instead, which will get the results from Lucene.
*
* @deprecated since 2.1 use the {@link SmartListManager} for complex queries
*/
List getContentInSpaceAuthoredBy(String spaceKey, String username);
Note that implementations of deprecated methods will result in compile warnings if they are not also marked as deprecated.
Fix Confluence Code to Avoid Deprecated Usage
When deprecating a class or method in Confluence, you must remove the majority – if not all – of the usages of that class or method
throughout Confluence. If you don't have the time or inclination to do so, you should probably not deprecate the method.
This makes sure that our own code doesn't violate our own deprecation unnecessarily, and also provides a sanity check for whether you
should deprecate something or not. If it is used too many times inside Confluence to begin changing, it could well be the same for external
code and you should think hard about deprecating such a frequently used method.
When Not to Deprecate
In some situations, maintaining deprecated methods may be impossible. For example:
You should never deprecate when...
The underlying model has changed, rendering any client of the old code obselete. For example if you move from Permission
getPermission() to List getPermissions(), the former method would return dangerously incorrect information if it were
maintained, and thus should be deleted.
The old way of doing things is dangerous. For example, if userManager.getAllUsers() is being removed because it kills the
server, we should not feel guilty forcing plugins to upgrade to the safe way of doing things.
You should make a judgement call when...
There would be significant effort required to maintain parallel, deprecated way of doing things for six months
You would be forced to write an ugly API because all the "right" method/class names are taken up with deprecated methods
(assume the new way of doing things will stick around forever)
DTDs and Schemas
This page is intended to host custom DTDs and schemas used internally by Confluence.
Name
Size
Text File hibernate-mapping-2.0.dtd
25 kB
Creator
Chris Kiehl
Creation Date
Comment
Jan 01, 2009 22:22
Exception Handling Guidelines
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
Randomly sorted guidelines.
1.
2.
3.
4.
5.
6.
7.
Don't catch Exception unless that's all you're having thrown to you.
Don't declare that you throw Exception ever.
Both rules #1 and #2 apply to RuntimeException as well.
Don't catch Throwable if you want to continue breathing.
Rule #4 applies to Error and any subclasses of Error as well.
Don't catch, log and rethrow.
Familiarise yourself with the standard RuntimeException subclasses (IllegalStateException, IllegalArgumentException,
UnsupportedOperationException, IndexOutOfBoundsException), and use them in preference to creating your own runtime exception
class.
For example, if the problem is that an object reference (or "pointer") which you didn't expect to be null is in fact null, why not
throw a NullPointerException?
8. If you explicity throw any RuntimeException in a method, document it in the method's @throws Javadoc like you would a checked
exception.
Meaningful exceptions
Where possible create, document and throw meaningful unchecked exceptions. For example, write this:
public class MyGroupManager
{
/**
* ...
* @throws InvalidGroupException if the group cannot be handled
*/
public void handleGroup(Group group) throws InvalidGroupException
{
if (!isValidGroup(group))
throw new InvalidGroupException("Group is invalid: " + group.toString());
// do something with the group
}
}
public class InvalidGroupException extends RuntimeException
{
// ...
}
In preference to this:
public class EvilGroupManager
{
public void handleGroup(Group group)
{
if (!isValidGroup(group))
throw new RuntimeException("Group is invalid: " + group.toString());
// do something with the group
}
}
The latter implementation is not as good because it gives the calling code very little discretion as to what kind of exceptions it wants to
handle.
Generating JSON output in Confluence with Jsonator
This document gives a technical overview of how JSON content can be generated in Confluence with the Jsonator framework.
The Jsonator framework makes it easy for XWork actions in plugins and Confluence core code to generate JSON for AJAX-related
functionality in the web UI. It is also possible to extend the Jsonator framework in Confluence core code to provide custom serialisation for
new types of objects.
Writing an action that generates JSON
Adding JSON generation to an existing action
Support serialisation objects
Customising object JSON serialisation
Related pages
Writing an action that generates JSON
To generate JSON from an XWork action, you map the result of the action to the 'json' result type. For example:
XWork action mapping
<action name="history" class="com.atlassian.confluence.user.actions.HistoryAction">
<result name="success" type="json"/>
</action>
In your XWork action class, make sure it implements Beanable and the JsonResult provided by Confluence will invoke the getBean()
method and automatically convert that into a JSON response.
Here is an example action class that returns a list of pages in the user's history:
Example JSON action class
public class HistoryAction extends ConfluenceActionSupport implements Beanable
{
private ContentEntityManager contentEntityManager;
private List<ContentEntityObject> history = new ArrayList<ContentEntityObject>();
private int maxResults = -1;
public String execute() throws Exception
{
UserHistoryHelper userHistoryHelper = new UserHistoryHelper(getRemoteUser(),
contentEntityManager, permissionManager);
history = userHistoryHelper.getHistoryContent(maxResults, new ContentTypeEnum[] {
ContentTypeEnum.PAGE });
return SUCCESS;
}
public Object getBean()
{
Map<String, Object> bean = new HashMap<String, Object>();
bean.put("history", history);
return bean;
}
public void setMaxResults(int maxResults)
{
this.maxResults = maxResults;
}
public void setContentEntityManager(ContentEntityManager contentEntityManager)
{
this.contentEntityManager = contentEntityManager;
}
}
The two relevant parts of this action are:
validation and data loading are done in the execute() method, like any other action
the getBean() returns an Object (normally a List or Map) for serialisation into JSON.
Here is some sample output from this action, reformatted for readability:
Sample JSON output
{
"history": [{
"id": "111774335",
"creationDate": "10 Dec 2007",
"title": "Confluence Services",
"creatorName": "pfragemann",
"spaceName": "Confluence Development",
"friendlyDate": "Aug 20, 2009",
"lastModifier": "ggaskell",
"spaceKey": "CONFDEV",
"type": "page",
"date": "Aug 20, 2009 15:48",
"lastModificationDate": "20 Aug 2009",
"url": "/display/CONFDEV/Confluence+Services"
},
{
"id": "111774337",
"creationDate": "10 Dec 2007",
"title": "High Level Architecture Overview",
"creatorName": "pfragemann",
"spaceName": "Confluence Development",
"friendlyDate": "Dec 10, 2007",
"lastModifier": "pfragemann",
"spaceKey": "CONFDEV",
"type": "page",
"date": "Dec 10, 2007 23:46",
"lastModificationDate": "10 Dec 2007",
"url": "/display/CONFDEV/High+Level+Architecture+Overview"
}]
}
Adding JSON generation to an existing action
The normal process of converting an existing action to return JSON is as follows:
1. Modify the action so it stores its data in a field on the object, if it doesn't already.
2. Make the action implement Beanable and return the action's data in the getBean() method.
3. Add a new XWork mapping (i.e. URL) for the action which maps all the action results to the "json" result type.
In Confluence core code, the new XWork mapping should be added to the /json XWork package.
In plugin code, the new XWork mapping can be added in any XWork package provided by the plugin.
As an example, the SearchSiteAction which provides Confluence's search functionality is accessed via the web UI in Confluence on
/searchsite.action. To convert this to a JSON action, the following was done:
1. The action was modified to implement Beanable and return the preexisting PaginationSupport class:
public Object getBean()
{
Map<String, Object> result = new HashMap<String, Object>();
result.put("total", paginationSupport.getTotal());
result.put("startIndex", paginationSupport.getStartIndex());
result.put("results", results);
return result;
}
2. A new mapping was added to the /json/ package to expose the JSON functionality on /json/searchsite.action
<action name="search" class="com.atlassian.confluence.search.actions.SearchSiteAction">
<result name="success" type="json"/>
<result name="error" type="json"/>
<result name="input" type="json"/>
</action>
Support serialisation objects
The DefaultJsonator includes implementations for the following types of objects (as of Confluence 3.3):
Primitive types and objects:
null
String
Number
Boolean
Byte
Collection (converted to JSON array with each element serialised individually)
Map (converted to JSON object, using key.toString() and serialising values indivudally)
Confluence objects:
ContentEntityObject - pages, blog posts, comments, etc.
Entity - users and groups
ValidationError
Message - i18n messages
Attachment
Breadcrumb
SearchResult
The fallback for an object which is not of any of the above types is a generic JavaBean serialiser which will convert the object to a JSON
object using any exposed JavaBean properties. Be careful of using this if objects returned from getBean() have non-trivial getter methods.
Customising object JSON serialisation
To add custom JSON serialisation in Confluence, you need to write a new implementation of the Jsonator interface and add it to the
DefaultJsonator configuration in the applicationContext.xml Spring context.
The Jsonator interface (as of Confluence 3.3) looks like this:
package com.atlassian.confluence.json.jsonator;
import com.atlassian.confluence.json.json.Json;
/**
* Interface to implement if you want to provide a method to create
* a JSON representation of an object
*/
public interface Jsonator<T>
{
/**
* Creates a {@link Json} representation of a given object
* @param object the object to be serialized
* @return Json JSON representation of the given object
*/
Json convert(T object);
}
Implementations must implement the single convert() method to return the associated JSON. The classes in the
com.atlassian.confluence.json.json package should be used to generate the JSON.
As an example, here is the implementation of AttachmentJsonator:
public class AttachmentJsonator implements Jsonator<Attachment>
{
private final HttpContext context;
private final ThumbnailManager thumbnailManager;
public AttachmentJsonator(HttpContext context, ThumbnailManager thumbnailManager)
{
this.context = context;
this.thumbnailManager = thumbnailManager;
}
public Json convert(Attachment attachment)
{
JsonObject json = new JsonObject();
json.setProperty("id", String.valueOf(attachment.getId()));
json.setProperty("name", attachment.getFileName());
json.setProperty("contentId", attachment.getContent().getIdAsString());
json.setProperty("version", String.valueOf(attachment.getAttachmentVersion()));
json.setProperty("type", attachment.getContentType());
json.setProperty("niceSize", attachment.getNiceFileSize());
json.setProperty("size", attachment.getFileSize());
json.setProperty("creatorName", attachment.getCreatorName());
json.setProperty("creationDate", attachment.getCreationDate());
json.setProperty("lastModifier", attachment.getLastModifierName());
json.setProperty("lastModificationDate", attachment.getLastModificationDate());
json.setProperty("url", context.getRequest().getContextPath() + attachment.getUrlPath());
json.setProperty("downloadUrl", context.getRequest().getContextPath() +
attachment.getDownloadPath());
json.setProperty("comment", attachment.getComment());
try
{
ThumbnailInfo thumbnailInfo = thumbnailManager.getThumbnailInfo(attachment);
json.setProperty("thumbnailUrl", thumbnailInfo.getThumbnailUrlPath());
json.setProperty("thumbnailHeight", thumbnailInfo.getThumbnailHeight());
json.setProperty("thumbnailWidth", thumbnailInfo.getThumbnailWidth());
}
catch (CannotGenerateThumbnailException e)
{
// ignore, attachment isn't the right type for thumbnail generation
}
return json;
}
}
Currently, new Jsonators must be registered in the applicationContext.xml file, and it is not possible to add new ones via plugins. Here
is the DefaultJsonator Spring bean declaration showing how the AttachmentJsonator is registered:
<bean id="jsonatorTarget" class="com.atlassian.confluence.json.jsonator.DefaultJsonator"
scope="prototype">
<constructor-arg index="0">
<map>
<!-- ... -->
<entry key="com.atlassian.confluence.pages.Attachment">
<bean class="com.atlassian.confluence.json.jsonator.AttachmentJsonator">
<constructor-arg index="0" ref="httpContext"/>
<constructor-arg index="1" ref="thumbnailManager"/>
</bean>
</entry>
<!-- ... -->
</map>
</constructor-arg>
</bean>
Related pages
Confluence UI Guidelines
Including Javascript and CSS resources
XWork-WebWork Module
Hibernate Sessions and Transaction Management Guidelines
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
Transaction Management
Transaction demarcation is provided by Spring, with a few wrinkles.
We wrap managers in transaction interceptors, but not DAOs.
We use whatever the default isolation level is for whatever database we're connecting to
We commit the transaction manually between performing an action, and displaying the view.
The last point is necessary because in some cases, we were sending redirect responses to the browser then committing the transaction. A
quick browser would request the redirect page before their transaction was committed, and view stale data as a result. By committing the
transaction before we render the view, we make sure that everything we expect to be in the database is in the database before the browser
has a chance to re-request it.
Manual Transaction Management
While you can normally use transaction interceptors configured through Spring, occasionally there is a need to programmatically initialise and
commit a transaction. You can use the Spring TransactionTemplate to do so, as shown in the following example.
TransactionDefinition transactionDefinition = new
DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRED);
new TransactionTemplate(transactionManager, transactionDefinition).execute(new
TransactionCallback()
{
@Override
public Object doInTransaction(TransactionStatus status)
{
// ... execute transactional code ...
return null;
}
});
The type of the transactionManager field in this example is
org.springframework.transaction.PlatformTransactionManager. You can get this injected by Spring into your component.
The propagation behaviour of your transaction should normally be PROPAGATION_REQUIRED. This will join an existing transaction if one is
present, or otherwise start a new one. Marking a transaction as read-only will help the performance of Hibernate by avoiding unnecessary
dirty checks on objects, if there isn't an existing read-write transaction in progress.
You can read more about the other propagation and transaction options in the Spring documentation.
Manual Transaction Management in Plugins
The plugin system currently uses a different version of Spring (2.5.6) to the version shipped with Confluence (2.0.8). For this reason, there is
a transaction abstraction provided by SAL that should be used for manual transaction management in plugins:
Object result = transactionTemplate.execute(new TransactionCallback()
{
@Override
public Object doInTransaction()
{
// ... execute transactional code ...
return null;
}
});
The type of the transactionTemplate variable is com.atlassian.sal.api.transaction.TransactionTemplate, and you can
get this dependency-injected into your component.
Unlike the direct Spring transaction management, you cannot set a custom propagation behaviour or other transaction attributes. The
implementation of SAL TransactionTemplate in Confluence always uses PROPAGATION_REQUIRED and marks the transaction as read-write.
Hibernate Sessions
Sessions are a Hibernate construct used to mediate connections with the database.
The session opens a single database connection when it is created, and holds onto it until the session is closed. Every object that is loaded
by Hibernate from the database is associated with the session, allowing Hibernate to automatically persist objects that are modified, and
allowing Hibernate to implement functionality such as lazy-loading.
Disconnected Objects
If an object is evicted from its session (for example via a clear, see below), or the session is closed while the object is still kept alive, the
object is "disconnected" from the session. A disconnected object will continue to work so long as you don't perform any operation that it
needs to go back to the database for, such as accessing a lazily-loaded collection.
If you see a LazyInitializationException, it means that a Hibernate-managed object has lived longer than its session.
Managed objects are not portable between sessions. Trying to load an object in one session then save it into another session will also result
in an error. (You can use Session.load() or Session.get() to re-introduce an object to a new session, but you're much better off fixing
whatever problem is causing you to try to move objects between sessions in the first place.
Caching
Storing hibernate objects in caches is a bad idea. By definition, a hibernate-managed object placed in a cache will outlive its session.
Even if caching such an object is safe now, it's quite likely that in the future we might switch some of its properties to be lazily-loaded, or
change code-paths so that properties that were previously being loaded before the object was cached aren't being loaded any more. The
LazyInitializationException errors that result rarely show up in tests, and are hard to diagnose and fix.
Hibernate maintains its own second-level cache (shared between Confluence nodes via Tangosol Coherence) that does not suffer from this
problem. Use it in preference to manually caching Hibernate data.
If you need to cache information from Hibernate, don't cache the Hibernate objects themselves. A useful alternative is to cache the object's
ID and class, and then retrieve the object in the context of the current session using Session.get(class, id). ID lookups go straight
through Hibernate's own second-level cache, so are (hopefully) efficient. The getHandle() and findByHandle() methods of the
AnyTypeObjectDao provide a helpful API for doing just this.
Flushing and Clearing
When the session persists its changes to the database, this is called "flushing". During a flush, each object associated with the session is
checked to see if it has changed state. Any object with changed state will be persisted to the database, regardless of whether the changed
objects are explicitly saved or not. You can configure Hibernate's flush behaviour, but the default (FlushMode.AUTO) will flush the session:
When you manually call flush() on the session
Before Hibernate performs a query, if Hibernate believes flushing is necessary for the query to get accurate results
When a transaction is committed
When the session is closed.
How long a flush takes is a function of the number of objects associated with the session. Thus, the more objects you load during the
lifetime of a session, the less efficient each query will be (as a flush will generally be done prior to each query). If you have some
long-running operation that gets slower and slower and slower as it runs, it's possible that the Hibernate session is the cause.
Operations that cycle through large numbers of objects should follow our guidelines for bulk operations in Hibernate.
Multi-threading
Hibernate sessions are not thread-safe. Not only does this mean you shouldn't pass a Hibernate session into a new thread, it also means
that because objects you load from a session can be called from (and call back to) their owning session, you must not share
Hibernate-managed objects between threads. Once again, try to only pass object IDs, and load the object freshly from the new thread's
own session.
Spring's transaction management places the Hibernate session in a ThreadLocal variable, accessed via the sessionFactory. All
Confluence DAOs use that ThreadLocal. This means that when you create a new thread you no longer have access to the Hibernate session
for that thread (a good thing, as above), and you are no longer part of your current transaction.
The Session In View Filter
Confluence uses the "Session in view" pattern for managing Hibernate sessions. The SessionInViewFilter opens a Hibernate session
which is then available for the entire web request. The advantages of this is that you avoid session-related errors:
The session lifecycle is uniform for every request
Hibernate objects remain "alive" for the whole request, thus you can still retrieve lazily-loaded data in Velocity templates
The disadvantages are:
Each request monopolises a database connection from the moment a request comes in, to the last byte sent to the client
Each session will end up associated with every object that is loaded for the duration of the request
Developers are often caught out by the way sessions behave when threads haven't come in through the web tier (i.e. Quartz jobs)
Non-Web Requests
Non-web requests do not automatically have a Hibernate session to work with, because they don't come in through the Session In
View Filter. This includes start-up events, quartz jobs, and any long-running task that spawns a new thread. As a result, a new session will be
opened when you make a call to a transaction-managed Spring object, and closed when that call returns.
A very common programming error in this context is to retrieve a collection of objects from a manager, then do something to each object. The
moment the call to the manager returns, all objects will be detached from their containing session. If you try to do anything to them after that,
you won't get the result you expected. I'm not sure if this sequence diagram helps, but here goes...
Consider moving such operations into the manager itself, so the whole operation will be wrapped in the one transaction. Alternatively, if
making everything run in separate transactions is what you want, have the job retrieve a collection of IDs, and pass those back to the
manager one by one.
Managing a Session Manually
In certain contexts, like a scheduled task in a plugin, you may want to create and manage a session manually. This can be done by using
Spring's HibernateTemplate as shown in the following example.
HibernateTemplate template = new HibernateTemplate(sessionFactory, true);
template.execute(new HibernateCallback()
{
@Override
public Object doInHibernate(Session session) throws HibernateException, SQLException
{
// ... execute database-related code ...
return null;
}
});
The type of the sessionFactory field in this example is net.sf.hibernate.SessionFactory. You can get this injected by Spring into
your component.
This code will create a new session if one is not already bound to the thread, execute the callback code, then close the session and release
the database connection back to the pool. Making direct calls to the SessionFactory is not recommended because it is very easy to leak
database connections if the sessions are not closed properly.
Hibernate Session and Transaction Management for Bulk Operations
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
This page describes the best practice for managing the Hibernate 2 flushing and clearing process when performing operations on large
numbers of objects in Confluence. For general information about dealing with Hibernate and Spring in normal situations, see the Hibernate
Sessions and Transaction Management Guidelines.
Understanding the underlying mechanisms Hibernate uses to track state is critical to understanding how this manual session and transaction
management works. These details of Hibernate are described below, a quick overview and sample code showing how to work around the
problem in practice.
The problem
The solution: how to ensure memory is released from Hibernate
Relationship between the transaction and the session
What happens when you flush the session
What happens when you clear the session
What happens when you commit the transaction
Related pages
The problem
One significant problem with ORMs like Hibernate is that they, by design, keep a reference to objects retrieved from the database for the
length of the session. When you are dealing an operation on a large dataset, this means that the objects added or retrieved by Hibernate
aren't eligible for garbage collection. In various places in Confluence, we work around this with manual session and transaction management
to limit the amount of memory needed for bulk operations.
The solution: how to ensure memory is released from Hibernate
In order to ensure you don't retain objects in the Hibernate session, you need to:
commit the active transaction
this automatically "flushes the session", synchronising any changes made to Hibernate objects to the database, as well as
committing those changes
clear the Hibernate session.
Using the native Hibernate and Spring library code, this amounts to the following code. You insert your batch processing logic inside the
TransactionTemplate execute method.
import
import
import
import
import
import
import
import
net.sf.hibernate.SessionFactory;
org.springframework.orm.hibernate.SessionFactoryUtils;
org.springframework.transaction.PlatformTransactionManager;
org.springframework.transaction.TransactionDefinition;
org.springframework.transaction.TransactionStatus;
org.springframework.transaction.interceptor.DefaultTransactionAttribute;
org.springframework.transaction.support.TransactionCallback;
org.springframework.transaction.support.TransactionTemplate;
public class MyAction
{
/** transaction settings that suspend the existing transaction and start a new one that will
commit independently */
private static final TransactionDefinition REQUIRES_NEW_TRANSACTION =
new DefaultTransactionAttribute(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
private final SessionFactory sessionFactory;
private final PlatformTransactionManager transactionManager;
public MyAction(SessionFactory sessionFactory, PlatformTransactionManager transactionManager)
{
this.sessionFactory = sessionFactory;
this.transactionManager = transactionManager;
}
public void execute() {
// iterate over your batches
for (final Object batch : batches)
{
new TransactionTemplate(transactionManager, REQUIRES_NEW_TRANSACTION).execute(new
TransactionCallback()
{
@Override
public Object doInTransaction(TransactionStatus status)
{
// ... process batch of objects ...
return null;
}
});
SessionFactoryUtils.getSession(sessionFactory, false).clear();
}
}
}
Committing the active transaction will ensure the data is flushed before committing. It will also ensure the executions list in the session
doesn't maintain a reference to any persistent objects. Clearing the session will ensure that any objects attached to the session in the
ID-to-object mappings will be no longer referenced. See below for more information about why these can cause problems.
In order to be confident that you are not committing changes made in the transaction by something higher in the stack, this code opens a
new transaction with the propagation setting of REQUIRES_NEW. This suspends changes on the higher-level transaction and commits only
those changes made at the lower level.
Because the session is cleared at the completion of each batch, changes made higher in the stack to objects attached to the
Hibernate session will be discarded. For this reason, you should normally run bulk operations on a separate thread. The thread should do
its own session management as described in the Hibernate session management guidelines. Most of the places in Confluence where bulk
operations occur run either on a separate thread or in upgrade tasks outside the scope of any request to avoid this problem.
Relationship between the transaction and the session
Confluence uses the HibernateTransactionManager which is provided with Spring 2.0. This is responsible for creating database
transactions when requested by an interceptor within the application.
When a transaction is opened, it is passed the session currently associated with the thread. If no session is active, a new one is created.
What happens when you flush the session
Flushing the session will run a "dirty check" on each object attached to the Hibernate session. This means any object which has been
retrieved by or added by Hibernate will have its internal state checked against the instance state map that Hibernate keeps internally. For
many objects, a dirty check is very expensive because it means checking the state of every dependent object as well as the object itself.
The dirty check executes inside SessionImpl.flushEntity which, if it determines some data has changed, will add a
ScheduledUpdate object to the list of updates maintained in the session. It also executes SessionImpl.flushCollections for all the
mapped collections on the object, which will register the fact that cached collections need to be updated with the changes.
Once all the attached objects have been checked for updates, the scheduled updates to objects and their collections are executed. This
occurs in the SessionImpl.execute method, which iterates through all the necessary updates, executes SQL, and empties the
collections.
If the query cache is enabled, which it always is in Confluence, Hibernate keeps a reference to every "execution" (insert, update or delete)
that it runs against the database until the transaction is committed. This means that flushing and clearing the session isn't sufficient to clear
all references to the attached objects; they will still be referenced by SessionImpl.executions until the transaction is committed.
What happens when you clear the session
Clearing the session empties the list of queued updates and the ID-based mapping that the session maintains for all attached objects. This
means any updates which weren't flushed will be lost.
The executions list which keeps track of the post-transaction executions will not be cleared when clearing the session. As mentioned
above, that means that flushing and clearing the session is not sufficient to clear all references to attached objects; they will still be strongly
referenced by the Session until the transaction is committed.
What happens when you commit the transaction
The transaction which is managed by the HibernateTransactionManager in Confluence, an instance of
net.sf.hibernate.transaction.JDBCTransaction, maintains a reference to the session it is associated with. When commit is called
on the transaction (usually by the outermost transaction interceptor), it flushes the session, calls session.connection().commit() to
send a commit statement directly via JDBC to the database, then finally calls SessionImpl.afterTransactionCompletion with a
boolean indicating whether the transaction succeeded.
The purpose of SessionImpl.afterTransactionCompletion is to execute any post-transaction tasks on statements which have
already been sent to the database. In the normal situation, this means updating persister (second-level) caches in Hibernate and releasing
any locks held on these caches.
In practice, this means committing the transaction is the only way to release all resources which are held by the Hibernate session to track
changes made during that transaction. Flushing the session is not sufficient. See above for recommendations on how to commit and clear
the session to ensure memory usage during bulk operations is not excessive.
Related pages
Hibernate Sessions and Transaction Management Guidelines
Spring IoC in Confluence
Persistence in Confluence
High Level Architecture Overview
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
The Disclaimer
This document represents the ideal arrangement of components in Confluence. This architecture should be the target of our refactoring, and
should inform any new features we add to the system.
The Goals
For the first three years of its development, little attention was paid to the high-level structure of the Confluence application. As such, it grew
organically and developed interesting quirks on the way. This document tries to make sense of Confluence from a high level, to make the
application easier to work with, easier to explain and easier to extend.
The goals are:
Clearly defined separation of concerns
Clearly defined interfaces between components
Clearly defined dependencies between components
Easier integration testing of components
Looser coupling
The Metaphor
Imagine an operating system.
At the lowest level you have the bootstrap code, which is required for going from a state of having nothing, to one where the rest of
the system can be loaded.
At the next level, the operating system provides device drivers, network abstractions and the like, generic services that any
application can use.
On top of those services you might run an application
Bootstrap
The DOC:Confluence Bootstrap Process is responsible for bringing up enough of Confluence that the rest of the system can be loaded. In
Confluence's case, this involves:
Locating the confluence home directory and the confluence.cfg.xml file
Determining whether the system is set up or not
Determining if we need to join a cluster or not
Loading the database connection configuration (either from the config file, or from the cluster)
Based on this information, the bootstrap process can determine what to do next, and provide enough configuration for the core services that
they know how to start up.
Bootstrap is implemented as a Spring context, in bootstrapContext.xml. It is loaded as a parent context to any subsequent Spring
context. It is available as a static singleton from BootstrapUtils.
Setup (a digression)
Confluence's in-browser setup requires a number of components that aren't used anywhere else. For example it needs a dummy plugin
manager so that i18n works before we have a real plugin manager available. Ideally, setup should be a separate Spring context that is
loaded when setup is required, and disposed of when setup is complete.
Currently this is not the case - setup components are loaded as part of the bootstrap context and remain indefinitely. To fix this will need
some work on the atlassian-setup component, which annoyingly conflates setup and bootstrap.
The Main Spring Context
Once the system has been bootstrapped, and setup has (at least) reached the point where we know how to connect to the database, the
main spring context is loaded as a child of the bootstrap context. The main Spring context, available as a static singleton from
ContainerManager, contains the remainder of Confluence's Spring configuration, loaded from a lot of different XML files in
WEB-INF/classes.
The list of XML files to load for the main Spring context is defined in the contextConfigLocation parameter in web.xml.
Loading these files in some specific order (as parent/child contexts) might make sense as a way of enforcing component boundaries, but I'm
not convinced the benefit is worth the effort.
See also: Spring Usage Guidelines
Services
These are generic services that you might consider useful to any application, like database access, caching, plugins, events,
indexing/searching, and so on. A good way to think of the service layer is to imagine a theoretical library called "atlassian-base", consisting
only of Confluence's bootstrap and service layers, which could be used as the basis for any new Atlassian web application.
Services can have dependencies on the bootstrap manager, and on each other, but there should never be a circular dependency between
services, and there should never be a tightly coupled dependency between a service and application code.
Interdependencies between services should be minimised. When introducing a dependency between services, ask if this dependency is
necessary or incidental.
Services are defined in XML config files in WEB-INF/classes/services.
One file per service
Each file should have a header comment describing the service, and explicitly declaring any dependencies on other services
Each file should be divided into "PUBLIC" and "PRIVATE" sections, delineating which components are part of the service's public
façade, and which are only for internal use
All beans defined in services must be explicitly wired. No autowiring.
In the future, once the service system has been bedded down, we might introduce some kind of naming convention for private beans to make
it harder to use them accidentally outside the context.
Confluence Services
Subsystems
Below the service layer is the Confluence application itself, which is divided into subsystems. More on this when I know what to do with them
myself.
Javadoc Standards
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
New Standard
Much of the Confluence codebase does not yet conform to this standard. Confluence developers should help the
Confluence codebase out by bringing any code they touch into line with this standard
Read Me First
You really, really should read the Sun guide to writing Doc comments: http://java.sun.com/j2se/javadoc/writingdoccomments/, especially the
advice about avoiding phrases like "This class.." or "This method..."
The Rules
All classes must have a doc comment
All public constants must have a doc comment
All public methods must have a doc comment except:
Methods that implement or override a method in an interface or superclass without adding any interesting behaviour beyond
what is already documented for the overridden method
Trivial getters or setters
A trivial corollary to the above rule: all methods declared in an interface must have doc comments.
Things You Should Document
Any side-effects of the method should be clear from the doc comment
@param tags for each parameter, even if the content is trivial
@returns tag for the return value, even if trivial
What type of object will be contained in any returned collection
What happens if any of the arguments supplied to the method are null (saying "Should never be null" in a {{@param}}is sufficient if
the behaviour is undefined, but probably bad)
Whether the method ever returns null, or if not, what it returns if there is no value
@throws tags for all declared exceptions, describing the circumstances they are thrown
@throws tags for any likely undeclared exceptions
a @since tag for any interface, or interface method
@see tags for other classes or methods that interact with the thing being documented in interesting ways, or for other ways to get the
same information
Tip
If you say something in the doc comment, you should have a test that proves it's true.
Things to avoid
Don't use the @author tag
Don't use the @version tag
Package Level Comments
...would be nice, but every time I've attempted to add them, I've come up against how badly our packages are structured. I'd say not to bother
right now.
Deprecation
So we don't keep stale methods around forever, we should always document when a method was deprecated. For example:
/** @deprecated since 2.3 use {@link Space#isValidSpaceKey} */
boolean isValidSpaceKey(String key);
For more information on commenting deprecated methods, see the Deprecation Guidelines
Logging Guidelines
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
The Purpose of Logging
Logging is an important function of any software and for Confluence, benefits the following groups of people in the manners described:
The Confluence server administrator — provides detailed information that lets them know if their Confluence server is running
correctly
Atlassian support — provides details and evidence of possible problems to help resolve customer issues
Developers — allows them to trace code execution without attaching a debugger
When you write any code in Confluence, you should ask yourself what you would want to see, from the point of view of the above three
people. If you were running a server, what would you want to be notified of? If you were handling a support case regarding some problem
with the system, what information would you need? If you were tracing a bug in the code, what would you want to see logged?
Loggers
Confluence uses SLF4J as a logging client.
private static final Logger log = LoggerFactory.getLogger(TheCurrentClass.class);
The current practice in Confluence is to create a static final logger called log (lower-case) in each class that needs to perform logging.
This may need to be addressed in the future. You will find some old code still uses Category.getInstance() or Logger.getLogger()
instead of LoggerFactory.getLogger(). Log4j is being phased out, and should be phased out as you find it.
There is a special log called startupLog in ConfluenceConfigurationListener, which defaults to logging at INFO level, and is used
to print out informational messages on system startup. Use this definition for startup messages:
private final static Logger startupLog =
LoggerFactory.getLogger("com.atlassian.confluence.lifecycle");
Log Levels
DEBUG Fine-grained information about what is going on within the system.
INFO Announcements about the normal operation of the system - scheduled jobs running, services starting and stopping, significant
user-triggered processes
WARN Any condition that, while not an error in itself, may indicate that the system is running sub-optimally
ERROR A condition that indicates something has gone wrong with the system
Default Log Level
The standard Confluence log (level WARN) is a way for Confluence to communicate with the server administrator. Anything we log to this file
will worry the administrator in some way. Logging at WARN level and higher should be reserved for situations that require some kind of
attention from the server administrator, and for which corrective action is possible.
We should assume that any time Confluence logs a WARN level message or higher, the customer will create a support case, and will expect
us to provide a solution that prevents that message from happening again.
Parameterised Logging and Performance
SLF4J provides parameterised logging capabilities. Parameterised logging allows you to specify parameters in your log statement which will
be evaluated only if the log is actually processed. Parameters are indicated in the logging statement with the string {}:
log.debug("Processed Page {} in Space {}", page, space);
This allows a more readable syntax than concatenating strings. It also yields better performance as the string will only be concatenated if
required (which in the example above, will occur only if DEBUG logging is enabled). This means that you do not need to wrap logging
statements in an ifDebugEnabled() statement, unless of course you need to do some work to create the parameters you are logging. You
should still avoid logging inside a tight loop and if you have to make sure it is at the debug level.
Context
You can rarely put too much context in a log message - avoid only just indicating that an operation failed. Indicate what operation you were
attempting when it failed, what object you were trying to act on, and why. Remember to log the exception, so a stack-trace can be logged.
We are not using parameterized logging here, as this is not possible when explicitly logging an exception
Bad:
log.error("Unable to save page: " + exception.getMessage());
Better:
log.error("Unable to save " + page + ": " + exception.getMessage(), exception);
Even better (assuming this information is available and relevant in the context. I am making this one up):
log.error("Unable to save " + page + " in updateMode=" +
isAutomaticSaveBoolean + " upon moving it from space " +
oldSpace + " to space "+newSpace+": " + exception.getMessage(), exception);
The Mapped Diagnostic Context
The slf4j MDC allows you to add contextual information to a ThreadLocal, which is then included with subsequent log messages. This is a
useful tool when you need to add the same contextual information to a lot of log messages.
For example, we currently use the MDC in the LoggingContextFilter to add the request URL and current user to the logging context for
every web request.
Example usage:
while (objectsToIndex.hasNext())
{
Searchable obj = (Searchable) objectsToIndex.next();
try
{
MDC.put("Indexing", obj.toString());
index(obj);
}
finally
{
MDC.remove("Indexing");
}
Logging Risks
Logging has the potential to introduce Heisenbugs into your code, because the act of turning logging on to look for an error changes the
code-paths around where the error is occurring. For example, logging often calls toString() on the objects it wants to log information
about. In turn, the toString() method of an object may have unexpected side-effects, for example causing some lazy-loaded Hibernate
property to be loaded in from the database.
Also, make sure to check that an object can't be null when calling a method on it. Especially when reporting errors, do go the extra mile to
query for null, e.g. like this:
String previousPageOutdatedId="[previous page variable is null]";
if (previousPage!=null) {
previousPageOutdatedId=previousPage.getOutdatedId();
}
log.error("Unable to revert to previous page: {}", previousPageOutdatedId);
instead of
log.error("Unable to revert to previous page {}", previousPage.getOutdatedId());
Logging progress
It is absolutely vital that some kind of logging is done during a long running task. At least at the start and at the end of it. Usually, a loop will
call a single operation very often. Make sure that - depending on how long a single call takes - you log each 10th, 100th, 1000th operation. If
possible add the complete number of operations that will have to be performed, e.g. in the form " executing delete user (2000/5301)"
Migrating to Velocity 1.5
Confluence trunk development (2.8) will be based on Velocity 1.5. The migration to the latest version of Velocity brings with it some issues
that Confluence developers need to be aware of.
Word tokens are no longer valid as the first argument to Velocimacros
In Velocity 1.4, the velocimacro syntax was changed to prevent the use of work tokens as the first argument to most directives (except for
defining the macro itself). This makes the following, common webwork structure fail to parse in Velocity 1.4 and beyond.
#bodytag (Select "label=$theLabel" "name='extraLevelName'" "list=levelTypes" "theme='notable'")
This means that you must quote the first argument to make it a proper string.
#bodytag ("Select" "label=$theLabel" "name='extraLevelName'" "list=levelTypes" "theme='notable'")
For these directives to work correctly with the new syntax a patched version of Webwork 2.1 is also required. Confluence now depends on
this custom version of Webwork 2.1.
When the old syntax is used, the following error will be produced (but with a different line, column and vm file):
org.apache.velocity.exception.ParseErrorException: Invalid arg #0 in directive at line 37, column
41 of /templates/publishingconfiguration.vm
Multi-line comments behave strangely in Velocimacros
Due to an apparent bug in Velocity 1.5 VELOCITY-537, multi-line comments in Velocimacros can cause ParseExceptions. Multi-line
macro comments have mainly been used in Confluence to control the output of extraneous whitespace during the rendering of a macro. To
work around this issue a new #trim() directive has been introduced that can be used to strip whitespace from macro rendering. This
unfortunately introduces a slight overhead to rendering as whitespace must be trimmed in every execution at runtime rather than stripped by
the lexer at template parsing time.
Using comments to control whitespace
#macro (globalLogoBlock)#*
*##if ($settingsManager.getGlobalSettings().isDisableLogo())#* render nothing
*##else#*
*#<a href="$req.contextPath/homepage.action"><img src="#logo("")" align="absmiddle"
border="0"></a>#*
*##end#*
*##end
Using the trim directive to control whitespace
#macro (globalLogoBlock)
#trim()
#if ($settingsManager.getGlobalSettings().isDisableLogo())
#else
<a href="$req.contextPath/homepage.action"><img src="#logo("")" align="absmiddle"
border="0"></a>
#end
#end
#end
We'll be able to revert to the previous method once VELOCITY-537 is fixed and integrated, although it's arguable that the new directive
makes for more maintainable macros.
Exceptions from method executions in macro parameters are no longer swallowed
Due to another bug in Velocity 1.3, exceptions that occur during a method execution in a macro parameter evaluation were swallowed silently
; the return value of such executions was null. Velocity 1.5 contains a fix for this which means its likely that we are going to run into
situations where pages which previously worked regardless of broken method calls are going to fail with a MethodInvocationException.
There's only one correct solution here: fix the broken method calls as we find them.
Equality test operator is more strict
In previous versions of Velocity testing for equality using just a single = worked. This has been made stricter in Velocity 1.5; you must use ==
otherwise a ParseException will be thrown.
Backwards compatibility with Velocity templates used in existing themes and plugins
We realise that some of the changes that Velocity 1.5 brings to Confluence could cause annoying compatibility problems and lots of work for
plugin maintainers, particulary the new Velocimacro syntax requirements. Confluence 2.8 will load all plugin templates using a special
resource loader which will attempt to automatically fix loaded templates to work with the new Velocity engine (
com.atlassian.confluence.util.velocity.Velocity13CompatibleResourceLoader). This does add some additional
overhead to plugin loading (the template is adjusted once at load time and then cached) but it will ease the burden on plugin developers
during this transitional period.
It is still a good idea for plugin authors to use the new Velocimacro syntax; updating your templates can be made easier by looking for the
info messages logged by the resource loader whenever it finds incompatible syntax.
Found incompatible Velocity 1.5 syntax in resource: [resource name]; [template fragment]
Dynamically loaded plugins only
For performance reasons, the compatibility layer is only applied to dynamically loaded plugins. Plugins loaded through
WEB-INF/lib will not have the compatibility processing applied.
Spring Usage Guidelines
These are guidelines related to the development of Confluence. The guidelines mainly apply to Atlassian employees, but
reading them should provide insight for third-party plugin developers as well, so we decided to make them public.
All new Spring component code should follow these guidelines. If you come across existing code that doesn't follow them, consider
refactoring it to do so.
For an overview of how Spring is used in Confluence, see Spring IoC in Confluence.
General Rules
Autowiring should only be used for transient, programatically constructed objects like actions and plugins. Never autowire anything
that's defined in a config file.
For singleton components, prefer constructor-based injection with final fields for dependencies.
Always specify indexes for constructor arguments.
If you have too many components to fit comfortably in a constructor, that's a sign the design is broken.
If a component is rarely used (i.e. the upgrade manager), consider giving it prototype scope instead of singleton.
Avoid circular dependencies. If you see a circular dependency, ask yourself:
Can this problem be solved using events?
Can this problem be solved by having one party register a callback?
Why use explicit constructor injection?
It fails fast. If a dependency is added, removed, missing, renamed, misspelled or the wrong type, you find out with a clear error
message during system startup not a NullPointerException much later.
With the amount of components in our Spring context, the introspection required to perform autowiring (or even to resolve the order
of components in a constructor) contributes significantly to the startup time of the application.
Transaction Management
Transactions should be wrapped around the manager layer.
Use the old-style Spring 1 transaction configuration syntax with explicit proxies - the Spring 2 pointcut syntax seems to slow down
container startup (and thus test-running) by a factor of ten
Profiling
Managers and DAOs should be wrapped by the profiling interceptor
If you're wondering whether a bean should be profiled or not, veer towards yes
Notes
Could we use some kind of funky XML Spring extension so we could declare a bean like this and have the extension apply the relevant
interceptors?
<bean name="blahManager" class="com.example.BlahManager" atl:txn="defaultManagerTxn"
atl:profile="yes"/>
Confluence Developer FAQ
This is a constantly updated FAQ listing questions and answers asked by people developing Confluence plugins and working with the
Confluence code base in general. For general questions, check Confluence FAQ.
If you have a question, please ask it as a comment and someone from Atlassian will reply. Comment threads will gradually
be merged back into this FAQ as needed. Please try to be as specific as possible with your questions.
Questions
No content found for label(s) faq_conf_dev.
RELATED TOPICS
Accessing Classes from Another Plugin
Disable Velocity Caching
When you are developing plugins for Confluence, it is often useful to disable the caching of the Velocity templates so that you don't have to
restart the server to see velocity changes.
Use the following steps to disable Velocity template caching in Confluence:
1. Shut down your Confluence server
2. Extract the velocity.properties file in your Confluence installation from confluence/WEB-INF/confluence-3.1.jar and copy it to another
location for editing.
3. Make the following changes to the velocity.properties file:
On all the lines that end with ...resource.loader.cache, set the values to false.
Set the class.resource.loader.cache to false. (If this entry does not exist, you can skip this step.)
Set velocimacro.library.autoreload to true. (Uncomment the line if necessary.)
4. Put the updated velocity.properties in confluence/WEB-INF/classes/. This file takes precedence over the one found in the
Confluence JAR file.
5. Start your Confluence server again.
Note that the Velocity macro libraries (macros.vm, menu_macros.vm) are only loaded once, when Velocity starts up. Therefore, any changes
to these files in Confluence will require restarting the application to take effect regardless of the caching settings above.
Enabling Developer Mode
Confluence's Developer Mode is a system property setting that tells Confluence to enable various debugging features that are not otherwise
exposed to users. To enable Developer Mode, you should start Confluence with the following system property set. See Configuring System
Properties for instructions.
-Datlassian.dev.mode=true
If you are writing a Confluence extension and want to check if Developer Mode is active, you can call
ConfluenceSystemProperties#isDevMode().
Developer Mode Features
Currently, enabling Developer Mode will activate the following features:
Prior to Confluence 2.0
Developer Mode not available in these releases
Confluence 2.0
The System Information page and 500 error page will contain an entry noting that Developer Mode is enabled
The "view as HTML" button will be made available in the WYSIWYG rich-text editor
Confluence 3.3
Secure administrator sessions will be disabled.
Encrypting error messages in Sybase
Adaptive server messages
http://infocenter.sybase.com/help/index.jsp?topic=/com.sybase.help.ase_12.5.svrtsg/html/svrtsg/svrtsg284.htm
How can I determine the context my macro is being rendered in?
For Confluence 2.10 (remember that?), we converted the display of the Jira Issues Macro from using a static HTML table to using a table
infused with jQuery goodness. Now we could add features that wouldn't have been possible without JavaScript, like the ability to sort issues
in the page without even reloading. That was pretty cool, but it also meant we had a new problem to deal with: macros can be rendered in
places that can't render JavaScript, such as in a feed reader or an email notification. In those cases, our beautifully redesigned macro would
look something like a puddle of goo.
We thought about how to get around this new problem, and decided the best approach would be to make it possible for macros to find out if
they are being rendered in an email or a feed, so they can display themselves appropriately. It was already possible for macros to find out if
they are being rendered in a PDF document or several other contexts. In Confluence 2.10, we made it possible for macros to find out that
they were being displayed in an email or feed, an addition to the previously defined contexts. Previously, macros being viewed in an email or
feed reader would have just had the render type "display", which is the default.
Now the Jira Issues Macro is able to render itself differently in display versus feed modes:
Okay, so how can you find out the current render context from within your macro? When creating a plugin that includes a macro module, you
return the HTML that the macro will display from the execute() method of the macro class. One of the parameters to the execute() method,
the one with type RenderContext, can be used to determine how the macro is being rendered.
Here's a sample execute method from a macro that prints out the current render context type:
public String execute(Map parameters, String body, RenderContext renderContext)
{
if(RenderContext.FEED.equals(renderContext.getOutputType()))
return "FEED render type";
else if(RenderContext.EMAIL.equals(renderContext.getOutputType()))
return "EMAIL render type";
else if(RenderContext.HTML_EXPORT.equals(renderContext.getOutputType()))
return "HTML_EXPORT render type";
else if(RenderContext.PREVIEW.equals(renderContext.getOutputType()))
return "PREVIEW render type";
else if(RenderContext.DISPLAY.equals(renderContext.getOutputType()))
return "DISPLAY render type";
else if(RenderContext.PDF.equals(renderContext.getOutputType()))
return "PDF render type";
else if(RenderContext.WORD.equals(renderContext.getOutputType()))
return "WORD render type";
else
return "some other render type";
}
If you used this sample macro on a page you were editing (by first installing the plugin that contains it), you could visit the preview tab to see
it output "PREVIEW render type". In the case of a more complex macro, you could, say, disable some UI elements when the
RenderContext.PREVIEW.equals(renderContext.getOutputType()) check is true. Using these checks is exactly how the Jira Issues Macro
decides whether to render itself using JavaScript or just stick with a basic HTML version.
Related
Event Listener Plugins
How does RENDERMODE work?
Speaking generally, macros will want to do one of three things with their body:
1. Pass the body through wiki->html conversion, then do something to it like stick some more HTML around it. (i.e. {panel})
2. Do something to the body, then pass it through wiki->html conversion (I don't really have an example of this)
3. Treat the body as data, not as wiki text. (i.e. {tasklist})
getBodyRenderMode() makes the first case above really easy, because the macro renderer will convert your body from wiki text to HTML
before it's passed to your macro's execute() method. That way your macro has ready-made HTML delivered to it, and you don't need to do
anything.
If you return RenderMode.ALL from getBodyRenderMode(), then the body is rendered the same as a Confluence page. You can,
however, return different values to only have a subset of renderings applied to your macro body: RenderMode.INLINE, for example, will
ignore things like paragraphs, headers or blockquotes.
So, for example, the {color} macro returns RenderMode.INLINE, since you can only really use {color} inside a paragraph.
If you are doing macros of type 2 or 3, you'll need to return RenderMode.NO_RENDER, which means the raw body is passed into your macro
with no pre-processing. You can then do whatever you want with it (including grabbing the SubRenderer component and converting it to wiki
text yourself).
Here's the relevant portion of the MacroRendererComponent, which does all the work, if Java code is more your thing:
private void processMacro(String command, Macro macro, String body,
Map params, RenderContext context,
StringBuffer buffer)
{
String renderedBody = body;
try
{
if (TextUtils.stringSet(body) && macro.getBodyRenderMode() != null
&& !macro.getBodyRenderMode().renderNothing())
{
renderedBody = subRenderer.render(body, context, macro.getBodyRenderMode());
}
String macroResult = macro.execute(params, renderedBody, context);
if (macro.getBodyRenderMode() == null)
{
buffer.append(macroResult);
}
else if (macro.isInline())
{
buffer.append(context.getRenderedContentStore().addInline(macroResult));
}
else
{
buffer.append(context.addRenderedContent(macroResult));
}
}
catch (MacroException e)
{
log.info("Error formatting macro: " + command + ": " + e, e);
buffer.append(makeMacroError(context, command + ": " + e.getMessage(), body));
}
catch (Throwable t)
{
log.error("Unexpected error formatting macro: " + command, t);
buffer.append(makeMacroError(context, "Error formatting macro: " + command + ": " +
t.toString(), body));
}
}
How do I associate my own properties with a ContentEntityObject?
How do I associate my own properties with a ContentEntityObject?
You will need the ContentEntityManager (see DOC:how to retrieve it). This manager allows you to store and retrieve arbitrary String values
associated with a ContentEntityObject.
Properties are stored as simple key/value pairs. We recommend that anyone writing a third-party plugin use the standard Java "reverse
domain name" syntax to ensure their keys are unique. Keys may be no longer than 200 characters.
// Set the property
contentPropertyManager.setText(page, "com.example.myProperty", "This is the value")
// Retrieve it
String myProperty = contentPropertyManager.getText(page, "com.example.myProperty")
getText and setText can store strings of arbitrary length (up to the size-limit for CLOBs in your database). There is also a getString
and setString which is slightly more efficient, but limited to 255 characters per value.
How do I autowire a component?
How do I autowire a component?
Most of the time, you don't have to. All plugins will have their 'primary' objects (The macro in a macro plugin, the XWork actions in an XWork
plugin, the RPC handler in an RPC plugin and so on...) autowired.
If you want to write an arbitrary object that is autowired, but that is not any particular plugin type itself, write a [Component Plugin Module].
The added advantage of this is that Confluence will then autowire other plugins with the component you have just written.
If, however, you find you need to autowire an arbitrary object with Spring components, use bucket.util.ContainerManager
bucket.container.ContainerManager.autowireComponent(myObject);
Where myObject is the object instance that you wish to be autowired.
How do I cache data in a plugin?
Confluence includes a caching API, atlassian-cache, which should be used instead of custom cache solutions. The provided API
ensures:
proper expiry of cache data (default expiry: 1 hour)
enables monitoring of cache usage by administrators
functions correctly in a Confluence cluster.
The remainder of this document describes how to use the atlassian-cache APIs.
Example
Below is a short example of some code using the atlassian-cache APIs.
public class ListPagesMacro extends BaseMacro {
private static final String CACHE_KEY = ListPagesMacro.class.getName() + ".dataCache";
private final CacheFactory cacheFactory;
private final PageManager pageManager;
public ListPagesMacro(CacheFactory cacheFactory, PageManager pageManager) {
this.cacheFactory = cacheFactory;
this.pageManager = pageManager;
}
private Cache getCache() {
return cacheFactory.getCache(CACHE_KEY);
}
public String execute(Map parameters, String body, RenderContext renderContext) {
String spaceKey = (String) arguments.get("spaceKey");
String result = (String) getCache().get(spaceKey);
if (result == null) {
result = renderPageList(spaceKey);
getCache().put(spaceKey, result);
}
return result;
}
private String renderPageList(String spaceKey) {
// ...
}
}
Instructions
To use the Atlassian Cache API, you first need to get a CacheFactory injected into your component (macro, action, etc). You do this by
adding a setter or constructor parameter to your component, depending on whether you are using setter-based or constructor-based
dependency injection.
To retrieve a cache from the cache factory, use a cache key which is unique to your plugin. We recommend using the fully qualified name of
the class which uses the cache, plus a name which describes the contents of the cache.
The returned Cache has an API very similar to Java's Map. You can call put(Object, Object) to store a value in the cache, and
get(Object) to look up a previously stored value.
In a single instance of Confluence, you can store any objects in the cache. In a clustered instance, you can only store keys and values which
implement Serializable.
Cache configuration
The cache configuration is determined by Confluence by default, with the ability for the Confluence administrator to change the settings at
runtime. The default expiry is one hour and the cache will store up to 1000 items. The least-recently used items will be automatically expired
or removed if space is needed for new items.
At the moment, it is not possible or recommended for plugins to change the size of caches that they use.
How do I check which Jar file a class file belong to?
How do I check which Jar file a class file belong to?
Knowing a jar file where a stack trace originates from can be handy when troubleshooting for library conflict. If you want to find out, this can
be easily done using user macros.
For example:
You can copy and paste the following code, which is the same as the screenshot above:
<pre>$action.getClass().getClassLoader().loadClass("org.apache.commons.lang.StringUtils").getProtectionDomain().toS
If the macro is run, it will print the path of the loaded jar file in your application server ie. the above user macro will print the file path to the jar
file where org.apache.commons.lang.StringUtils class belongs.
How do I convert wiki text to HTML?
How do I convert wiki text to HTML?
This depends on where you want to do it:
In a macro...
You will need the SubRenderer (see how to retrieve it).
The SubRenderer has two render methods: one that allows you to specify a specific RenderMode for the rendered content, and another
that uses the current RenderMode from the RenderContext.
If you just want the body of your macro rendered, you can have this done for you by the macro subsystem by having your
macro's getBodyRenderMode method return the appropriate RenderMode.
In some other component...
You will need the WikiStyleRenderer (see how to retrieve a component).
The WikiStyleRenderer has a convertWikiToHtml method that takes the wiki text you wish to convert, and a RenderContext. If you are
converting the text in the context of some ContentEntityObject (for example within a page or blog post), then you can call
contentEntityObject.toPageContext() to retrieve its RenderContext. Otherwise pass in a new PageContext().
How do I develop against Confluence with Secure Administrator Sessions?
Secure administrator sessions is a security feature introduced in Confluence 3.3. This provides an additional layer of authentication for
administration functions. If you are developing a plugin for Confluence 3.3 or later, you will need to take note of the information below.
The information on this page relates to plugin development using AMPS/Atlassian Plugin SDK. Atlassian Maven Plugin
Suite (AMPS) is part of the Atlassian Plugin SDK. We strongly recommend that you use the Atlassian Plugin SDK to build
plugins for our Confluence. It includes a number of features that simplify the plugin development process.
You must run Confluence (3.3 and later) in developer mode to develop against Confluence using AMPS or deploy a plugin using the
Atlassian Plugin SDK. If you do not do this, you will receive an exception when deploying the plugin. This is because the plugin will be
expecting the plugin upload screen when it is uploaded, but will get the secure administration session authentication screen instead.
Please note, if you use AMPS to develop against Confluence, it will start Confluence in developer mode. This will automatically disable the
secure administrator session authentication checks, so you should not encounter any problems. You also will not run into this problem if you
are developing against Confluence 3.2 and earlier, as these versions do not have the secure administrator sessions feature.
Plugin Development
Currently only WebWork Modules are protected by the temporary secure administrator sessions. Other plugin types, such as REST services
or servlets are not checked for an administrator session.
All webwork modules mounted under /admin will automatically be protected by secure administrator sessions. To opt out of this protection
you can mark your class or webwork action method with the WebSudoNotRequired annotation. Conversely, all webwork actions mounted
outside the /admin namespace are not protected and can be opted in by adding the WebSudoRequired annotation.
Both of these annotations work on the class or the action method. If you mark a method with the annotation, only action invocations invoking
that method will be affected by the annotations. If you annotate the class, any invocation to that class will be affected. Sub-classes inherit
these annotations.
How do I display the Confluence System Classpath?
At times, you may see an error like this:
java.lang.NoSuchMethodError:
org.apache.commons.fileupload.servlet.ServletFileUpload.setFileSizeMax
Cause: The Java classpath has another module (jar) somewhere that overrides the one shipped with Confluence.
Solution:
1. Please run the following to list all modules available to the class loader:
http://path-to-confluence/admin/classpath.action
2. Check for and resolve duplicate jars.
How do I ensure my plugin works properly in a cluster?
Clustering in Confluence is supposed to work mostly transparently for plugin developers. However, there are a few things to be aware of in
more advanced plugins.
Please note: this guide is always open for expansion – please comment on the page if you think something should be added here.
Installation of plugins in a cluster
Testing your plugin in a cluster
Caching in a cluster
Scheduled tasks
Cluster-wide locks
Event handling
Installation of plugins in a cluster
Installation for the Confluence cluster administrator is the same as with a single instance. Uploading a plugin through the web interface will
store the plugin in the PLUGINDATA table in the database, and ensure that it is loaded on all instances of the cluster.
Cluster instances must be homogeneous, so you can assume the same performance characteristics and version of Confluence running on
each instance.
Testing your plugin in a cluster
It is important to test your plugin in a cluster if you want to make sure it works properly. Setting up a cluster with Confluence is as easy as
setting up two new instances on the same machine with a cluster license – it shouldn't take more than ten minutes to test your plugin
manually.
If you don't have access to a cluster license for Confluence, contact Atlassian Developer Relations ([email protected]) to obtain a
developer cluster license for testing.
Caching in a cluster
In many simple plugins, it is common to cache data in a field in your object – typically a ConcurrentMap or WeakHashMap. This caching will
not work correctly in a cluster because updating the data on one instance will make the cached data on the other instance stale.
The solution is to use the caching API provided with Confluence, Atlassian Cache. For example code and a description of how to cache data
correctly in Confluence, see:
How do I cache data in a plugin?
Both keys and values of data stored in a cache in a Confluence cluster must implement Serializable.
Scheduled tasks
Without any intervention, scheduled tasks will execute independently on each Confluence instance in a cluster. In some circumstances, this
is desirable behaviour. In other situations, you will need to use cluster-wide locking to ensure that jobs are only executed once per cluster.
The easiest way to do this is to use the perClusterJob attribute on your job module declaration, as documented on the Job Module page.
In some cases you may need to implement locking manually to ensure the proper execution of scheduled tasks on different instances. See
the locking section below for more information on this.
Cluster-wide locks
The locking primitives provided with Java (java.util.concurrent.Lock, synchronized, etc.) will not properly ensure serialised
access to data in a cluster. Instead, you need to use the cluster-wide lock that is provided through Confluence's ClusterManager API.
Below is an example of using a cluster-wide lock via ClusterManager.getClusteredLock():
ClusteredLock lock = clusterManager.getClusteredLock(getClass().getName() + ".taskExecutionLock");
if (lock.tryLock()) {
try {
log.info("Acquired lock to execute task");
executeTask();
}
finally {
lock.unlock();
}
}
else {
log.info("Task is running on another instance");
}
Event handling
By default, Confluence events are only propagated on the instance on which they occur. This is normally desirable behaviour for plugins,
which can rely on this to only respond once to a particular event in a cluster. It also ensures that the Hibernate-backed objects which are
often included in an event will still be attached to the database session when interacting with them in your plugin code.
If your plugin needs to publish events to other nodes in the cluster, we recommend you do the following:
1. Annotate the event class with ClusterEvent
2. Important: construct the event setting the source parameter to this, the originating class instance
3. When handling the event, check the value returned by event.getSource() to determine whether the event originated on the
current instance or another instance. This field is transient in the event base class, which means:
on the originating instance, the source will not be null
on other instances in the cluster, the source will be null.
Your event handling code should take care to check the source and handle the event correctly depending on whether it came from the
current node or not.
Like clustered cache data, events which are republished across a cluster can only contain fields which implement Serializable or are
marked transient. In some cases, it may be preferable to create a separate event class for cluster events which includes object IDs rather
than Hibernate-backed objects. Other instances can then retrieve the data from the database themselves when processing the event.
Confluence will only publish cluster events when the current transaction is committed and complete. This is to ensure that any data you store
in the database will be available to other instances in the cluster when the event is received and processed.
RELATED TOPICS
Technical Overview of Clustering in Confluence
How do I cache data in a plugin?
Confluence Clustering Overview
How do I find Confluence Performance Tests?
Since the 2.10 release, Performance tests can be found here
How do I find Confluence Test Suite?
All our Tests are stored inside the 'source release' you can download if you have a commercial licence from atlassian main site
When you expand the 'source', you can locate the following:
unit and integration test
...confluence-project/confluence/src
acceptance test
...confluence-project/src/test
How Do I find enabled and disabled plugins in the Database?
Enabled Plugins
Plugins from the repository, once installed are stored in table PLUGINDATA. They are enabled after install.
Disabled Plugins
All Plugins (bundled and from the repository) that have been disabled have an entry in table BANDANA where BANDANAKEY is
plugin.manager.state.Map.
For Example if the pagetree macro had been installed but is currently disabled would be reflected in BANDANAVALUE
<map>
<entry>
<string>bnpparibas.confluence.pagetree</string>
<boolean>false</boolean>
</entry>
</map>
It turns out that the <boolean> expression is not really evaluated. When the plugin name is present in the map it is
considered as disabled
How do I find information about lost attachments?
You may like to use the findattachments.jsp which should detect missing attachments.
For Confluence 3.x, please download the corresponding script attached to Resolve Missing Attachments in Confluence
Simply copy it to confluence/admin/findattachments.jsp and access it at <confluence_base_url>/admin/findattachments.jsp
Below is an example of the result generated by http://<confluence_base_url>/admin/findattachments.jsp
Beginning search...
Missing attachment: <path>/attachments/3477/279/1, filename: Final-OdysseyCodingConventions.doc, filetype: Word
Document
As you can see in the above example, the script will report:
Location of the attachment missing
Full Name of the attachment
File type recognised :
PDF Document
Image
XML File
HTML Document
Text File
Word Document
Excel Spreadsheet
PowerPoint Presentation
Java Source File
Zip Archive
How do I find the logged in user?
How do I find the logged in user?
This can be retrieved easily from the com.atlassian.confluence.user.AuthenticatedUserThreadLocal class which will give you
the current logged in user as a com.atlassian.user.User object.
User user = AuthenticatedUserThreadLocal.getUser();
Should the user not be logged in the user object will be null.
How do I get a reference to a component?
How do I get a reference to a component?
Confluence's component system is powered by Spring, but we've done a lot of nice things to make it easier for developers to get their hands
on a component at any time.
Autowired Objects
If your object is being autowired (for example another plugin module or an XWork action), the easiest way to access a component is to add a
basic Java setter method.
For example, if you need a SpaceManager simply add the following setter method. This setter will be called when the object is created.
public void setSpaceManager(SpaceManager spaceManager)
{
this.spaceManager = spaceManager;
}
You can also write you own components which are automatically injected into your plugins in the same way. See
[Component Plugins] for more detail
Non-autowired Objects
If your object is not being autowired, you may need to retrieve the component explicitly. This is done via the ContainerManager like so:
SpaceManager spaceManager = (SpaceManager) ContainerManager.getComponent("spaceManager");
How do I get hold of the GET-Parameters within a Macro?
If you want to get hold of the GET-Parameters within your Macro (Java-Code), you need to access the Java servlet.
First add a dependency for the servlet-api to your pom.xml:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
<scope>provided</scope>
</dependency>
Now you can easily access every parameter you wish like this:
String showParam = ServletActionContext.getRequest().getParameter("show");
How do I get hold of the HttpServletRequest?
How do I get hold of the HttpServletRequest?
HttpServletRequest request = ServletActionContext.getRequest();
if (request != null)
{
// do something here
}
You should always assume that ServletActionContext.getRequest() will return null. ServletActionContext is only populated if the request
comes in through WebWork. There are a number of circumstances in which it will not be populated, either because a web request has come
in through some other path, or because there was no web request in the first place:
AJAX requests that come in via the DWR servlet
SOAP/XML-RPC requests
Scheduled tasks, including the sending of email notifications
Treat ServletActionContext as a bonus. If it's populated you can do neat things with it, but don't rely on it.
How do I get my macro output exported to HTML and PDF?
This is only applies to Confluence 2.7 and higher.
How do I get my macro output exported to HTML and PDF?
Macros such as the chart macro may produce images, which should be included in HTML and PDF exports. This is now possible if macros
delegate the responsibility of storing the output to Confluence.
ExportDownloadResourceManager
The ExportDownloadResourceManager is responsible for managing the reading and writing of macro output. Confluence uses this manager
to lookup/retrieve macro output for downloads and exports. Hence, if you would like your macro to support exports, it is required that you use
this manager to retrieve the correct writer to write to.
ExportDownlaodResourceManager
/**
* Returns a DownloadResourceReader for reading the stored output of the previous execution of
a macro.
* Typically used by HTML and PDF export, macro content downloads.
*
* @param userName the user who is viewing the macro output. Must be the same as the user who
created the macro
* output with {@link #getResourceWriter(String, String, String)}, or an
UnauthorizedDownloadResourceException
* will be thrown.
* @param resourcePath the relative URL of the resource including the application context
path. For example,
* "/confluence/download/temp/chart1756.png". It must be the same path from the {@link
DownloadResourceWriter}.
* @throws UnauthorizedDownloadResourceException if the user requesting the macro output is
different to the user
* who created it
* @throws DownloadResourceNotFoundException if a stored macro output associated with this
resource path cannot be
* found
*/
public DownloadResourceReader getResourceReader(String userName, String resourcePath, Map
parameters)
throws UnauthorizedDownloadResourceException, DownloadResourceNotFoundException
/**
* Returns a DownloadResourceWriter for storing output of a macro in a temporary location.
* This should be typically called by macros that generate output such as images and would
like their
* output to be exported correctly.
*
* @param userName the user who is creating the macro output.
* @param prefix the prefix of the macro output's name
* @param suffix the suffix of the macro output
*/
public DownloadResourceWriter getResourceWriter(String userName, String prefix, String suffix)
The following is an example of how to retrieve the output stream for which you can use to write your macro output to.
public class ExampleMacro extends BaseMacro
{
private ExportDownloadResourceManager exportDownloadResourceManager;
public void setExportDownloadResourceManager(ExportDownloadResourceManager
exportDownloadResourceManager)
{
this.exportDownloadResourceManager = exportDownloadResourceManager;
}
public String execute(Map parameters, String body, RenderContext renderContext) throws
MacroException
{
// parse parameters and generate the output/image
....
// get the current user
User user = AuthenticatedUserThreadLocal.getUser();
String userName = user == null ? "" : user.getName();
// get the resource writer
DownloadResourceWriter writer = exportDownloadResourceManager.getResourceWriter(userName,
"example", "png");
OutputStream outputStream = writer.getStreamForWriting();
try
{
// write to the output stream
.....
}
finally
{
// close the output stream
if(outputStream != null)
outputStream.close();
}
return "<img src=\"" + writer.getResourcePath() + "/>";
}
}
How do I get the base URL and ContextPath of a Confluence installation?
How do I get the base URL of a Confluence installation?
When you are writing Confluence plugins, sometimes you need to create an absolute URL, with the full "http://..." included. To do that, you
need to determine what the URL path is up to the root of the Confluence web application.
Confluence attempts to guess the correct base URL for the site during setup. You can change it in the site's General Configuration.
How do I determine the base URL and context path?
There are two ways of doing this. If you have a more recent version of Confluence, you can get it all in one spot. Older versions will require
joining two separate string values.
Recent versions of Confluence
Recent versions of Confluence give the full path you need from one location.
First you need the SettingsManager object (see how to retrieve it), then call the following method:
String baseUrl = settingsManager.getGlobalSettings().getBaseUrl();
Older versions of Confluence
Older versions of Confluence have what you need split into two parts, the base URL and the context path.
The base URL is the URL for the root of your Confluence site. For example, the base URL for this site is
http://confluence.atlassian.com. If you have installed Confluence somewhere other than the root directory of the webserver, for
example http://www.example.com/confluence, then your base URL would be http://www.example.com/confluence.
First you need the BootstrapManager (see how to retrieve it) then simply call the following method:
String baseUrl = bootstrapManager.getBaseUrl();
To complete the URL, you will need to add the context path. The context path is the path to Confluence relative to the root directory of the
webserver. For example, the context path for this site is an empty string, because it is deployed at the root. The context path for a
Confluence instance deployed at http://www.example.com/confluence would be /confluence.
To get it, use:
String contextPath = bootstrapManager.getWebAppContextPath()
To get the full path, just do this:
String fullPath = baseUrl + contextPath;
In Confluence 2.0 and earlier the method was called bootstrapManager.getDomain(). The getDomain() method
was deprecated in favour of getBaseUrl() in Confluence 2.1, because the latter name better describes the information it
returns.
How do I get the information about Confluence such as version number, build number,
build date?
Information about Confluence, such as the version number, build number and build date, can be retrieved from the GeneralUtil object.
You can use GeneralUtils public accessors to retrieve public static variables:
versionNumber
buildDate
buildNumber
In Java
String versionNumber = GeneralUtil.getVersionNumber();
String buildNumber = GeneralUtil.getBuildNumber();
String buildDate = GeneralUtil.getBuildDateString();
or
Date buildDate = GeneralUtil.getBuildDate();
In Velocity
$generalUtil.versionNumber
$generalUtil.buildNumber
$generalUtil.buildDateString
For instance, part of the Confluence footer is generated in the footer.vm file:
(Version: $generalUtil.versionNumber Build:#$generalUtil.buildNumber $generalUtil.buildDateString)
In Wiki markup
User Macros can include the Velocity markup given above. For example, create a macro called 'version' with no body and the contents:
$generalUtil.versionNumber
You can use this user macro in a page like this:
Congratulation, you're running Confluence version {version}!
User Macros
Unknown macro: {version}
How do I get the location of the confluence.home directory?
How do I get the location of the confluence.home directory?
First you need the BootstrapManager (see DOC:how to retrieve it) then simply call the following method:
String confluenceHome = bootstrapManager.getConfluenceHome();
The BootstrapManager also has a getConfiguredConfluenceHome method. This method is used during system startup
to determine the location of confluence.home from first principles. There is no reason for you to call this method:
getConfluenceHome should be sufficient.
How do I load a resource from a plugin?
The recommended way to get resources from the classpath in Confluence is:
InputStream in = com.atlassian.core.util.ClassLoaderUtils.getResourceAsStream(filename, this);
ClassLoaderUtils tries a couple of different classloaders, something we've occasionally found necessary in some application servers.
How do I make my attachments open in a new window or a tab?
How do I make my attachments open in a new window/tab?
You need to add a TARGET = "_blank" to the <a href> HTML tag.
The A element used in HTML denotes an anchor which is a hypertext link.
The HREF attribute specifies a hypertext link to another resource, such as an HTML document or a JPEG image.
The TARGET attribute is used with frames to specify the frame in which the link should be rendered. If no frame with such a name exists, the
link is rendered in a new window unless overridden by the user. Special frame names begin with
an underscore. The frame used in this document is the _blank which renders the link in a new, unnamed window.
<Source: http://www.w3.org/TR/html401/struct/links.html >
For example, by using the HTML code below, clicking on the link "a new window" will open the "newwindow.html" page in a new window:
<A href="newwindow.html" _TARGET="_blank"_>a new window</A>
Open attachments listed for a Space
To open the attachments listed from the Browse Space->Attachments tab, in a new window, the
..\confluence\src\webapp\pages\listattachmentsforspace.vm file under your <Confluence-install> directory has to be
modified. Below are the listed steps:
1. Locate the following block of code in the listattachmentsforspace.vm file:
foreach ($attachment in $pagedAttachments)
<tr #alternateRowColors() id="attachment_$attachment.id">
<td width="1%" nowrap valign="top"><a
name="$generalUtil.urlEncode($attachment.content.realTitle)-attachment-$generalUtil.urlEncode($attachment.fileName)
("/pages/includes/attachment_icon.vm")</a> <a
href="$req.contextPath$attachment.downloadPathWithoutVersion"
>$attachment.fileName</a></td>
<td width="1%" nowrap valign="top">$attachment.niceFileSize</td>
<td width="1%" nowrap valign="top">#usernameLink($attachment.creatorName)
#if ($attachment.creatorName!=$attachment.lastModifierName) ($action.getText('last.modified.by')
#usernameLink($attachment.lastModifierName)) #end</td>
<td width="1%" nowrap
valign="top">$dateFormatter.format($attachment.lastModificationDate)</td>
<td>#contentLink2 ($attachment.getContent() true false)</td>
</tr>
#end
2. In the line below:
<td width="1%" nowrap valign="top"><a
name="$generalUtil.urlEncode($attachment.content.realTitle)-attachment-$generalUtil.urlEncode($attachment.fileName)
("/pages/includes/attachment_icon.vm")</a> <a
href="$req.contextPath$attachment.downloadPathWithoutVersion"
>$attachment.fileName</a></td>
add the parameter TARGET = "_blank" to the <a href> HTML tag, which will cause the URL specified in the href parameter to open in a new
window or a new tag depending upon the option set in the browser. So the line above will be modified to:
<td width="1%" nowrap valign="top"><a
name="$generalUtil.urlEncode($attachment.content.realTitle)-attachment-$generalUtil.urlEncode($attachment.fileName)
("/pages/includes/attachment_icon.vm")</a> <a
href="$req.contextPath$attachment.downloadPathWithoutVersion"
TARGET =
"_blank">$attachment.fileName</a></td>
Open attachments listed for a Page
To open the page attachments listed from the Page's Attachment(s) tab, in a new window, the
..\confluence\src\webapp\pages\viewattachments.vm file under your <Confluence-install> directory has to be modified. Below
are the listed steps:
1. Locate the following block of code in the viewattachments.vm file:
<td nowrap valign="top"><a
name="$generalUtil.htmlEncode($generalUtil.urlEncode($page.title))-attachment-$generalUtil.htmlEncode($generalUtil.
("/pages/includes/attachment_icon.vm")</a> <a href="$generalUtil.htmlEncode("
${req.contextPath}${attachment.downloadPathWithoutVersion}")"TARGET =
"_blank">$generalUtil.htmlEncode($attachment.fileName)</a></td>
2. In the line below:
<a
name="$generalUtil.htmlEncode($generalUtil.urlEncode($page.title))-attachment-$generalUtil.htmlEncode($generalUtil.
("/pages/includes/attachment_icon.vm")</a> <a href="$generalUtil.htmlEncode("
${req.contextPath}${attachment.downloadPathWithoutVersion}")">$generalUtil.htmlEncode($attachment.fileName)</a>
add the parameter TARGET = "_blank" to the <a> HTML tag, which will cause the URL specified in the href parameter to open in a new
window or a new tag depending upon the option set in the browser. So the line above will be modified to:
<a
name="$generalUtil.htmlEncode($generalUtil.urlEncode($page.title))-attachment-$generalUtil.htmlEncode($generalUtil.
("/pages/includes/attachment_icon.vm")</a> <a href="$generalUtil.htmlEncode("
${req.contextPath}${attachment.downloadPathWithoutVersion}")" TARGET =
"_blank">$generalUtil.htmlEncode($attachment.fileName)</a>
How do I prevent my rendered wiki text from being surrounded by paragraph tags?
How do I prevent my rendered wiki text from being surrounded by <p> tags?
When wiki text is converted to HTML, the level of conversion is determined by the RenderMode set within the RenderContext. Understanding
RenderMode is quite important, so you should familiarise yourself with the documentation linked above.
There are two render modes that are useful if you want to avoid the output being placed inside paragraph tags:
RenderMode.INLINE will suppress the rendering of all block-level HTML elements, including paragraphs, blockquotes, tables and lists.
Inline elements such as text decorations, links and images will still be rendered.
RenderMode.suppress( RenderMode.F_FIRST_PARA ) will render block-level elements as usual, but if the first such element is a
paragraph, no paragraph tags will be drawn around it. This is useful if you're placing your output inside a <div>.
If you are writing a macro, you will also need to return true from your macro's isInline method.
How do I tell if a user has permission to...?
How do I tell if a user has permission to...?
When you're writing a Confluence plugin, it's important to check that the user has permission to do the operations your plugin is performing.
Confluence does not enforce security for you, it's up to your code to perform these checks.
There are two places you might want to check permissions:
In Java Code
In Velocity Templates
In Java Code:
You will need:
1. the User object of the user whose permissions you want to check (How do I find the logged in user?)
2. the permissionManager component from Spring (How do I get a reference to a component?)
The PermissionManager has quite a few methods (Javadoc), but the most important are:
/**
* Determine whether a user has a particular permission against a given target.
*
* @param user the user seeking permission, or null if the anonymous user is being checked
against
* @param permission the permission to check
* @param target the object that the permission is being checked against. If this object is
null, the method
*
will return false
* @return true if the user has this permission, false otherwise
* @throws IllegalStateException if the permission being checked against does not apply to the
target
*/
boolean hasPermission(User user, Permission permission, Object target);
/**
* Determine whether a user has permission to create an entity of a particular type within a
given container.
*
* <p>The container is the natural container of the object being created. For example, a
comment is contained
* in a page, which is contained within TARGET_APPLICATION.
*
* @param user the user seeking permission, or null if the anonymous user is being checked
against
* @param container the target that the object is being created within. If this object is
null, the method
*
will return false
* @param typeToCreate the type of object being created (see above)
* @return true if the user has permission, false otherwise
* @see com.atlassian.confluence.core.ContentEntityObject#getType()
* @throws IllegalStateException if the permission being checked against does not apply to the
target
*/
boolean hasCreatePermission(User user, Object container, Class typeToCreate);
Simple Permissions
Generally you're going to be asking the question: "Does some user have permission to do something to some target?" For example: "Does
BOB have permission to VIEW this PAGE?", "Does JANE have permission to REMOVE this ATTACHMENT?" These questions map to the
hasPermission() method above.
The various values of "something" are all constants of the Permission class listed in this Javadoc. At the time this document was written,
the permission 'verbs' are:
Permission.VIEW
Permission.EDIT
Permission.EXPORT
Permission.REMOVE
Permission.SET_PERMISSIONS
Permission.ADMINISTER
So to check if your user has permission to edit a particular page, the call is:
permissionManager.hasPermission(myUser, Permission.EDIT, thePage)
For global permissions, the 'target object' is considered to be the Confluence application itself. There is a special target,
TARGET_APPLICATION that represents the application as a whole. So to check if someone is a global administrator, call:
permissionManager.hasPermission(myUser, Permission.ADMINISTER,
PermissionManager.TARGET_APPLICATION
Create Permissions
Checking if someone has the ability to create an object (page, blogpost, space, etc) is a little more complicated. Every object is created
inside some other object. Comments and Attachments are created inside Pages or BlogPosts. Pages are created inside Spaces. And
Spaces are crated inside TARGET_APPLICATION.
So to check if someone can create something, the question is: "Does this user have permission to create this KIND OF OBJECT, in this
CONTAINER?" In Java, kinds of objects are represented by their class, so to see if a user can create a comment inside a particular page,
you'd call:
permissionManager.hasCreatePermission(myUser, containingPage, Comment.class)
And to check if the user has permission to create spaces globally:
permissionManager.asCreatePermission(myUser, PermissionManager.TARGET_APPLICATION, Space.class)
In Velocity Templates
While all of the above is very powerful, it's a bit complicated to deal with in a Velocity file. There is an object in the default velocity context
called $permissionHelper which has a bunch of useful methods on it. All the methods do pretty much what you'd expect them to do, so I'll
just link to the Javadoc:
http://www.atlassian.com/software/confluence/docs/api/latest/com/atlassian/confluence/security/PermissionHelper.html
And give a simple example:
#if ($permissionHelper.canEdit($remoteUser, $action.page))
<b>You have Edit Permission for this Page</b>
#end
How to switch to non-minified Javascript for debugging
Although you should always serve minified Javascript, temporarily running Confluence with non-minified Javascript can be useful for
debugging.
A quick and simple way to do this is run Confluence with the following VM setting:
-Datlassian.webresource.disable.minification=true
If you're using IntelliJ IDEA you can enter this at RunEdit ConfigurationsTomcat Server(your instance name)VM Parameters
Remember to remove the parameter when you're finished debugging.
how to use Wysiwyg plugin in my new page?
I want to use rich text to write mail in Confluence ,and i want to use wysiwyg
like this:
HTTP Response Code Definitions
HTTP Response Codes
Below is a list of HTTP Response codes and their meaning.
This information was obtained from:
HTTP Response Code Definitions
Code
Meaning
100
Continue
101
Switching Protocols
200
OK
201
Created
202
Accepted
203
Non-Authoritative Information
204
No Content
205
Reset Content
206
Partial Content
300
Multiple Choices
301
Moved Permanently
302
Found
303
See Other
304
Not Modified
305
Use Proxy
307
Temporary Redirect
400
Bad Request
401
Unauthorized
402
Payment Required
403
Forbidden
404
Not Found
405
Method Not Allowed
406
Not Acceptable
407
Proxy Authentication Required
408
Request Time-out
409
Conflict
410
Gone
411
Length Required
412
Precondition Failed
413
Request Entity Too Large
414
Request-URI Too Large
415
Unsupported Media Type
416
Requested range not satisfiable
417
Expectation Failed
500
Internal Server Error
501
Not Implemented
502
Bad Gateway
503
Service Unavailable
504
Gateway Time-out
505
HTTP Version not supported
HTTP Headers
It would be useful to obtain information on HTTP response headers. If you are using Mozilla Firefox, you can download an 'add-ons'
(extension) called LiveHTTPHeaders which will allow you to capture this information. If you are using Internet Explorer, you can use
DebugBar instead.
Live HTTP Headers Installation Instructions
For Live HTTP Headers, please do the following:
1. Download and install the Plugin
2. Restart Firefox
3. Go to Tools in the menu bar and click on Live HTTP Headers. This will trigger the functionality.
Now try accessing the Confluence main page and all HTTP request headers, cookies descriptions (such as the seraph authentication
'seraph.os.cookie') will be logged in the pop-up window. Please save this information in a text file, use the 'Save All' option.
DebugBar Installation Instructions
Run the downloaded installation file. After installing the DebugBar, the toolbar should automatically display on the next IE startup. If not, you
might need to show the toolbar in IE by clicking on View > Explorer Bar then select DebugBar
Download Live HTTP Headers add-on
Download DebugBar
I am trying to compile a plugin, but get an error about the target release
I am trying to compile a plugin, but get an error about the "target release"
When compiling plugins and using version 1.5 of the JDK, the following error may appear:
javac: target release 1.3 conflicts with default source release 1.5
SOLUTION
The solution is essentially to tell your compiler to target Java 1.3. How to do this will differ depending on what compiler you are using, but
generally, something like this will work:
javac -target 1.3 <other options here>
If you are using Maven to build your project, try adding the following to your project.properties or build.properties file:
# Set the javac target to 1.3
maven.compile.target=1.3
maven.compile.source=1.3
If the solutions above do not resolve this issue and you are using an older version of Confluence, try the following approach:
Open the src/etc/plugins/build.xml file and in the line that looks similar to the following one, change its target parameter from "1.3" to "1.5":
<javac destdir="${library}/classes" target="1.3" debug="${debug}" deprecation="false"
optimize="false" failonerror="true">
RELATED TOPICS
Confluence Plugin Guide
FAQ Home
REV400 - How do I link to a comment?
This page is a draft in progress and visible to atlassian-staff only.
This page is a draft for the release of Confluence 4.0. It should be made public after Confluence 4.0 is made available.
Linking to a comment can only be done by making use of the 'permalink' icon that is displayed on comments when viewing a Confluence
page.
This can be programatically retrieved easily from
com.atlassian.confluence.spaghettinoodle? class which will give you the
permalink URL as a com.atlassian.noodle? object.
10 PRINT "INSERT CODE HERE"
20 GOTO 10
See this documentation for instructions on how to manually copy and paste the permalink for a comment: Linking to Comments.
The link above is the live commercial version doco link – should have this content:
http://confluence.atlassian.com/display/DOC/REV400+-+Linking+to+Comments.
In versions of Confluence prior to Confluence 4.0, comments could be displayed by using a special "comment id". This
functionality has been removed and is no longer available Confluence 4.0 or later versions.
Troubleshooting Macros in the Page Gadget
Macros were originally designed to only be used in a Confluence instance. Rendering macros in the Confluence Page Gadget outside of a
Confluence instance can result in minor quirks that require some workarounds to resolve. Please note, the workarounds described below are
written for users who are confident developing in Confluence. Do not attempt any of the procedures below, if you do not have any experience
in this area.
Please see the Confluence Page Gadget documentation for the full list of working/non-working macros.
On this page:
My AJAX call isn't executing my callback
Example Macros:
Some links are not being directed back to Confluence
Examples:
The gadget isn't resizing
I want the macro to render differently in the Page Gadget
Examples:
I would like to style my macro/theme differently in the Page Gadget
My AJAX call isn't executing my callback
The page gadget uses a proxy to execute AJAX requests back to Confluence. This means that, in some cases, an AJAX call that previously
worked in Confluence may not work in the page gadget.
If you include an error handler like this:
AJS.$.ajax({
url: /your/url,
type: "GET",
data: {
pageId: pageId
},
success: function(data) {
executeSuccess(data);
},
error: function(err, text) {
AJS.log('Error loading page ' + text);
}
});
You may see the following error:
Failed to parse JSON
This generally occurs when your action returns raw html, while the page gadget expects JSON by default. To fix just add the following to the
ajax call.
dataType : 'text',
Example Macros:
Page tree
Some links are not being directed back to Confluence
When rendering the page gadget, Confluence attempts to fix all the urls so that they point directly to Confluence rather than using relative
urls. However, this fix does not work for any links that are added dynamically (for example in the pagetree macro). Thus to fix this problem
there is a javascript function available that will cycle through the links and fix any that have been added. So after any links are added just
execute the following javascript:
if (AJS.PageGadget && AJS.PageGadget.contentsUpdated)
{AJS.PageGadget.contentsUpdated();
}
Examples:
Advanced Macros (recently updated)
Page tree
The gadget isn't resizing
As in the previous section, the page gadget needs to be notified when the page has increased/decreased in size. Executing the above code
will also ensure that the page content fits into the page gadget.
I want the macro to render differently in the Page Gadget
Sometimes you would like to render a completely different view in the page gadget. To achieve this you can use the Page Gadget render
type com.atlassian.confluence.renderer.ConfluenceRenderContextOutputType#PAGE_GADGET. This render type notifies the
macro that it is being rendered in the context of a page gadget. This method is used when rendering the tasklist and gadget macros.
Examples:
Tasklist
Attachments Macro
I would like to style my macro/theme differently in the Page Gadget
In the gadget iframe we have included:
<body class="page-gadget">
... content ....
</body>
So if you would like to style your macro or theme specifically for the page gadget you can use the body.page-gadget selector.
Examples:
Easy Reader Theme
What's the easiest way to render a velocity template from Java code?
What's the easiest way to render a velocity template from Java code?
Use VelocityUtils. You will need to provide VelocityUtils with the name of the template you want to render, and a map of parameters that will
be made available within the template as $variables in velocity.
Confluence has a default set of objects for Confluence velocity templates. These are required for most Confluence velocity macros to work
properly. To obtain this context, you should call MacroUtils.defaultVelocityContext();.
// Create the Velocity Context
Map context = MacroUtils.defaultVelocityContext();
context.put("myCustomVar", customVar);
context.put("otherCustomVar", otherCustomVar);
// Render the Template
String result = VelocityUtils.getRenderedTemplate("/com/myplugin/templates/macro.vm", context);
RELATED TOPICS
Rendering Velocity templates in a macro
WoW Macro explanation
[Macro Plugins]
What class should my macro extend?
What class should my macro extend?
It should extend com.atlassian.renderer.v2.macro.BaseMacro, not com.atlassian.renderer.macro.BaseMacro.
What class should my XWork action plugin extend?
What class should my XWork action plugin extend?
WebWork actions must implement com.opensymphony.xwork.Action. However, we recommend you make your action extend
ConfluenceActionSupport, which provides a number of helper methods and components that are useful when writing an Action that works
within Confluence.
Other action base-classes can be found within Confluence, but we recommend you don't use them - the hierarchy of action classes in
Confluence is over-complicated, and likely to be simplified in the future in a way that will break your plugins.
What is Bandana? One form of Confluence Persistence
Bandana is Atlassian's hierarchical data storage mechanism, it breaks objects into XML and stores them, to be retrieved later... uses xstream
and a little hierarchical magic under the covers and has another strange Atlassian codename. It is one way to persist data inside your plugin.
It is good for global config types of data.
It uses XStream to serialize Java strings (and objects?) to and from XML.
Examples:
The BandanaManager can be acquired via Confluence's (Spring's) dependency injection.
Data in this case is written to: confluence-data-dir/config/confluence-global.bandana.xml
Writing data:
bandanaManager.setValue(new ConfluenceBandanaContext(), GmapsManager.GOOGLE_MAPS_API_KEY,
updateApiKey);
Retrieving data:
public String getGoogleApiKey()
{
return (String) bandanaManager.getValue(new ConfluenceBandanaContext(),
GmapsManager.GOOGLE_MAPS_API_KEY);
}
See also: Persistence in Confluence
What is the best way to load a class or resource from a plugin?
What is the best way to load a resource from the classpath?
Because of the different ways that application servers deal with class-loading, just calling this.getClass().getResourceAsStream()
might not work the same everywhere Confluence is deployed. To help, we have a utility method that checks the various classloaders in a
predictable order:
InputStream in = com.atlassian.core.util.ClassLoaderUtils.getResourceAsStream(filename, this)
Inside Plugins
Because plugins may be dynamically loaded, each plugin may have its own classloader, separate from the main Confluence application. This
makes loading resources like properties files from inside a plugin JAR a little tricky.
If the class from which you are loading the resource is in the same jar as the resource file itself (i.e. it's all part of the same plugin), you can
use ClassLoaderUtils as above, and everything will work fine.
However, if you are trying to load the file from a different plugin, or from the main application code, you'll need an instance of the
pluginManager from spring:
InputStream in = pluginManager.getDynamicResourceAsStream(filename)
(That said, you must now ask yourself why you're loading an arbitrary resource from some other plugin? It seems like a really bad idea to me.
If the plugin wants to export that resource to the rest of the application, it should provide some way of getting at it itself.)
Within a Confluence macro, how do I retrieve the current ContentEntityObject?
Within a Confluence macro, how do I retrieve the current ContentEntityObject?
You can retrieve the current ContentEntityObject (ie the content object this macro is a part of), as follows:
public String execute(Map parameters, String body, RenderContext renderContext) throws
MacroException {
// retrieve a reference to the body object this macro is in
if (!(renderContext instanceof PageContext))
{
throw new MacroException("This macro can only be used in a page");
}
ContentEntityObject contentObject = ((PageContext)renderContext).getEntity();
...
Note that this method might return null if there is no current content object (for example if you are previewing a page that has not been added
yet, or if a remote user is rendering a fragment of notation).
Confluence Developer Forum
The Confluence Developer Forum is a place for the discussion of extending and customising Confluence.
The forum has been replaced by Confluence Development topics on Atlassian Answers.
The Developer FAQ
Some questions come up often. Make sure you've checked the Confluence Developer FAQ first.
About the Participants
When taking part in Atlassian Answers, please keep in mind that a lot of the people on the list don't work for Atlassian at all, and are
answering questions because they're nice people.
Preparing for Confluence 4.0
If you would like to send us any feedback, please email it to [email protected].
Who should read this?
This documentation is intended for Confluence plugin developers. This documentation will walk through:
Creating a new Confluence 4.0 Macro
Upgrading and Migrating an Existing Confluence Macro to 4.0
What's changing in Confluence 4.0?
Both the content storage format and the user experience for adding macros will change in Confluence 4.0. This release will introduce a new
WYSIWYG editor with a completely different architecture based on XHTML. Also, the end user experience will change as follows:
The new editor will be entirely WYSIWYG. There will be no wiki markup editor.
Instead of wiki markup code being displayed in WYSIWYG editor, we now display macros inside macro placeholders. There are
two macro placeholders, one for inline macros (they have no body content) and one for body macros.
Macro placeholders include a macro property panel which allows you to click 'edit' inside the macro placeholder to launch the macro
browser.
We provide a set of tools to help you test your macro:
You can insert wiki markup using the 'Insert Wiki Markup' dialog in the 'Insert' menu. Confluence will convert the wiki markup to
HTML and insert it into the editor for you. You will find this dialog useful in testing your macro migration.
How do I get Confluence 4.0?
Confluence 4.0 Milestone 14 is available in the Atlassian public maven repository and can be accessed with the following details:
<dependency>
<groupId>com.atlassian.confluence</groupId>
<artifactId>confluence</artifactId>
<version>4.0-m17</version>
</dependency>
Alternatively if you are using AMPS you can set your confluence.version property to accordingly.
For all versions after 4.0-m16 you will need to use a confluence.data.version of 3.2
<confluence.version>4.0-m17</confluence.version>
<confluence.data.version>3.2</confluence.data.version>
Confluence 4.0 For Plugin Developers Webinar
On the 15th of February, 2011 the Confluence team held a webinar for Confluence plugin developers.
Click the image below to play the webinar.
You can download the webinar video here.
Frequently Asked Developer Questions
Can we get access to the Confluence 4.0 milestones source code?
If you are a plugin developer, an existing customer or partner you have access to the 4.0 milestone releases including the source from the
usual locations.
For customers concerned about getting "locked in," will there be a way to export the content in a usable format?
Confluence will continue to support exporting content to Word, PDF and HTML. Additional to this, space and site administrators can export
content to XML. Finally, the Universal Wiki Converter is a plugin that allows customers to move from one wiki to another.
Are nested macros supported in the new macro editor?
Yes, they are. Macros will be placed within other macros using "Macro Placeholders". Macro Placeholders are a visual representation of a
macro on a page. Below is an example of what two would look like in the new editor:
What is happening to user macros?
This question can be answered in two parts:
1. Upgrading user macros from 3.x:
All user macros will continue to work when upgrading to 4.0. However, in order for end users to insert user macros Administrators
will need to enable their user macros in the Macro Browser if they hadn't already done so in Confluence 3.4 or 3.5. This can be done
via the User Macro Admin Console. Administrators have the option of only showing user macros to other administrators. It is strongly
advised that you recreate your user macros in 4.0 format.
2. Creating new user macros:
User macros will no longer have an output type of Wiki Markup. User macros can still embed other macros using the new XHTML
syntax. The documentation for this syntax is not yet complete, we will update and link to it form this page once it is.
Will it be possible to have pre/post hooks during the storage->view conversion process?
We acknowledge that would be very useful for things like security plugins, deciding if the end user can have the plugin rendered (even if they
can view the page). However, we don't have this yet and are unsure if this will make it into the initial version of 4.0. We are focused on
improving the editing experience rather than including more plugin points to the rendering subsystem (at this point). Your feedback for this
would be appreciated.
Will we have access to edit the source code?
Please see the editor FAQ page for this.
How come you can not support old remote API for content import/export? Can it go through the to/from XHTML
converter?
One of the main reasons to switch to an XHTML based storage format is that we can't convert from HTML to wiki markup in a reliable way.
The remote API will offer an additional method that allows for one way conversion of wiki markup to the XHTML based storage format.
What other resources do we provide?
Please watch the Development Releases page for pre-release versions of Confluence 4.0 that you can use for testing purposes.
For more information about changes in Confluence 4.0, from a user's point of view as well as a developer's point of view, please
read Planning for Confluence 4.0 and Confluence 4.0 Editor FAQ.
Related Content
No content found for label(s) conf4_dev.
Macro Tutorials for Confluence 4.0
Creating a new Confluence 4.0 Macro
Extending the macro property panel - an example
Preventing XSS issues with macros in Confluence 4.0
Providing an image as a macro placeholder in the editor
Upgrading and Migrating an Existing Confluence Macro to 4.0
We also have Plugin points for the editor in 4.0.
Creating a new Confluence 4.0 Macro
See Preventing XSS issues with macros in Confluence 4.0 for information on how to prevent XSS issues with your
plain-text macro.
Overview
This tutorial will show how to create an XHTML macro for Confluence 4.0 and how to conduct basic operations on the storage format.
The following concepts will be covered:
1. The Macro interface.
2. Using the provided API to interact with storage format.
3. The xhtml-macro module descriptor.
Some Confluence plugin development knowledge is assumed:
1. Creating a plugin using the Atlassian Plugin SDK.
2. Installation / testing / debugging of plugins.
3. Basic knowledge of the Confluence object model.
For this tutorial we will create a macro that will build a list of macros on the current page and output them.
Prerequisites
1. Create a new Confluence macro plugin development project using the Atlassian Plugin SDK.
2. Remove the skeleton macro created with the Plugin SDK.
3. If you are using maven2 for your dependency management, then update the confluence.version in your pom to reflect
Confluence 4.0:
<properties>
<confluence.version>4.0-m17</confluence.version>
<confluence.data.version>3.2</confluence.data.version>
</properties>
Implementing the new Macro interface
The interface we will be implementing is the new Macro interface provided by Confluence 4.0. This is
com.atlassian.confluence.macro.Macro and it requires three methods to be implemented.
1. Implementing the getBodyType and getOutputType methods
These two methods specify whether the macro has a body (and the type of body if it does have one) and the output type, be it block or inline.
The macro we implement today will not have a body and will have block output.
Body and output type implementations
@Override
public BodyType getBodyType()
{
return BodyType.NONE;
}
@Override
public OutputType getOutputType()
{
return OutputType.BLOCK;
}
2. Injecting the XhtmlContent utilities class
In order to assist in operating with the XHTML content in Confluence 4.0 we have provided a number of API methods. In this tutorial we will
use this class:
com.atlassian.confluence.xhtml.api.XhtmlContent
Macros support constructor injection, so we will use this to get a reference to the XthmlContent utils.
Declaration and injection of XhtmlContent utils
private final XhtmlContent xhtmlUtils;
public XhtmlMacroDemo(XhtmlContent xhtmlUtils)
{
this.xhtmlUtils = xhtmlUtils;
}
3. Implementing the execute method
The execute method has a similar signature to macros implemented against the V2Renderer, with all of the parameters meaning pretty
much the same thing. We will break the implementation of this method into three parts:
1. Getting the body of the page we are on.
2. Traversing the storage format to extract the macros on the page.
3. Building the output for display.
3.1. Getting the body content for the page we are on
The process used here will likely change in the very near future, in favour of getting the details directly from the
ConversionContext.
In order to get the content for the page we are on, we will use a new method introduced in Confluence 4.0 ContentEntityObject.getBodyAsString() - this will return the storage format for the given entity, which will be in XHTML.
Getting the body contents
// We will eventually be able to get the page out of the conversion context, but for the time
being we have to do it this way.
if (!(conversionContext instanceof DefaultConversionContext))
{
return "";
}
DefaultConversionContext defaultConversionContext = (DefaultConversionContext) conversionContext;
String body = ((PageContext)
defaultConversionContext.getRenderContext()).getEntity().getBodyAsString(); // A String of XHTML
text
3.2. Traversing the storage format to extract the macros on the page
The XHTML storage format is not too much use to us at the moment, unless we want to parse it ourselves (no - we don't...). What we can
now do is utilise the XhtmlContent utility we had injected earlier to traverse the XHTML and call our custom handler, which will add it to a
list of MacroDefinition instances we are creating.
The API call used XhtmlUtils.handleMacroDefinitions(...) will return all MacroDefinition instances on the page, including
nested macros in the storage format.
Using the API to traverse the storage format
final List<MacroDefinition> macros = new ArrayList<MacroDefinition>();
try
{
xhtmlUtils.handleMacroDefinitions(body, conversionContext, new MacroDefinitionHandler()
{
@Override
public void handle(MacroDefinition macroDefinition)
{
macros.add(macroDefinition);
}
});
}
catch (XhtmlException e)
{
throw new MacroExecutionException(e);
}
3.3. Building the output for display
Now that the bulk of the work is done we just need to build up the information we have collected and output it. For this we will simply list the
macro name and whether or not it has a body in a table.
Build up the output
StringBuilder builder = new StringBuilder();
builder.append("<p>");
if (!macros.isEmpty())
{
builder.append("<table width=\"50%\">");
builder.append("<tr><th>Macro Name</th><th>Has Body?</th></tr>");
for (MacroDefinition defn : macros)
{
builder.append("<tr>");
builder.append("<td>").append(defn.getName()).append("</td><td>").append(defn.hasBody()).append("</td>");
builder.append("</tr>");
}
builder.append("</table>");
}
else
{
builder.append("How did this happen - I am a macro, where am I?!?!?!");
}
builder.append("</p>");
return builder.toString();
4. Registering the new macro
Now that we have the implementation for the macro, we will need to register this as a module in our atlassian-plugin.xml file. In
Confluence 4.0 we introduce the new module descriptor for XHTML macros that implement the
com.atlassian.confluence.macro.Macro interface.
This module descriptor looks very much like the macro module descriptor, with a different name: xhtml-macro.
Module descriptor for our macro
<xhtml-macro name="xhtml-macro-demo"
class="com.atlassian.confluence.plugin.xhtml.XhtmlMacroDemo"
key="xhtml-macro-demo">
<category name="development"/>
<parameters/>
</xhtml-macro>
5. The output
After installing and referencing this macro on a page, you should see the following:
Conclusion
In this tutorial you saw how to create a macro against the new Confluence 4.0 macro interface and how to traverse the XHTML storage
format to extract MacroDefinition instances.
Appendix
XhtmlMacroDemo class
XhtmlMacroDemo.java
package com.atlassian.confluence.plugin.xhtml;
import
import
import
import
import
import
import
import
import
com.atlassian.confluence.content.render.xhtml.ConversionContext;
com.atlassian.confluence.content.render.xhtml.DefaultConversionContext;
com.atlassian.confluence.content.render.xhtml.XhtmlException;
com.atlassian.confluence.macro.Macro;
com.atlassian.confluence.macro.MacroExecutionException;
com.atlassian.confluence.renderer.PageContext;
com.atlassian.confluence.xhtml.api.MacroDefinition;
com.atlassian.confluence.xhtml.api.MacroDefinitionHandler;
com.atlassian.confluence.xhtml.api.XhtmlContent;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
public class XhtmlMacroDemo implements Macro
{
private final XhtmlContent xhtmlUtils;
public XhtmlMacroDemo(XhtmlContent xhtmlUtils)
{
this.xhtmlUtils = xhtmlUtils;
}
@Override
public BodyType getBodyType()
{
return BodyType.NONE;
}
@Override
public OutputType getOutputType()
{
return OutputType.BLOCK;
}
@Override
public String execute(Map<String, String> parameters, String bodyContent, ConversionContext
conversionContext) throws MacroExecutionException
{
// We will eventually be able to get the page out of the conversion context, for the time
being we have to do it this way.
if (!(conversionContext instanceof DefaultConversionContext))
{
return "";
}
DefaultConversionContext defaultConversionContext = (DefaultConversionContext)
conversionContext;
String body = ((PageContext)
defaultConversionContext.getRenderContext()).getEntity().getBodyAsString();
final List<MacroDefinition> macros = new ArrayList<MacroDefinition>();
try
{
xhtmlUtils.handleMacroDefinitions(body, conversionContext, new
MacroDefinitionHandler()
{
@Override
public void handle(MacroDefinition macroDefinition)
{
macros.add(macroDefinition);
}
});
}
catch (XhtmlException e)
{
throw new MacroExecutionException(e);
}
StringBuilder builder = new StringBuilder();
builder.append("<p>");
if (!macros.isEmpty())
{
builder.append("<table width=\"50%\">");
builder.append("<tr><th>Macro Name</th><th>Has Body?</th></tr>");
for (MacroDefinition defn : macros)
{
builder.append("<tr>");
builder.append("<td>").append(defn.getName()).append("</td><td>").append(defn.hasBody()).append("</td>");
builder.append("</tr>");
}
builder.append("</table>");
}
else
{
builder.append("How did this happen - I am a macro, where am I?!?!?!");
}
builder.append("</p>");
return builder.toString();
}
}
atlassian-plugin.xml file
atlassian-plugin.xml
<atlassian-plugin key="${project.groupId}.${project.artifactId}" name="${project.artifactId}"
plugins-version="2">
<plugin-info>
<description>${project.description}</description>
<version>${project.version}</version>
<vendor name="${project.organization.name}" url="${project.organization.url}"/>
</plugin-info>
<xhtml-macro name="xhtml-macro-demo"
class="com.atlassian.confluence.plugin.xhtml.XhtmlMacroDemo"
key="xhtml-macro-demo">
<category name="development"/>
<parameters/>
</xhtml-macro>
</atlassian-plugin>
Related Content
No content found for label(s) conf4_dev.
Extending the macro property panel - an example
You can download the complete source for this macro here:
https://bitbucket.org/rthomas/confluence-status-light-macro/overview
Overview
The macro property panel allows a user to remove or edit the currently selected macro, this page will show you how to extend this to add
custom buttons.
We are going to create a status light macro that will render in the editor with additional buttons in the property panel to change the current
status - from 0 to 100 percent.
to
Geting Started
To get started, lets create a Confluence plugin using AMPS
mac-pro:plugins ryan$ atlas-create-confluence-plugin
...
Define value for groupId: : com.atlassian.confluence.plugin
Define value for artifactId: : status-light
Define value for version: 1.0-SNAPSHOT: <enter>
Define value for package: com.atlassian.confluence.plugin: <enter>
Confirm properties configuration:
groupId: com.atlassian.confluence.plugin
artifactId: status-light
version: 1.0-SNAPSHOT
package: com.atlassian.confluence.plugin
Y: <enter>
Once this has been created you should have a folder called status-light (or whatever you decided to name your artifact).
Open the pom.xml file in here in your favourite IDE.
First we need to modify the default pom so that we can build against Confluence 4.0, change the properties in the pom.xml file to read like
this:
<properties>
<confluence.version>4.0-m17</confluence.version>
<confluence.data.version>3.2</confluence.data.version>
</properties>
Next what we will do is copy in all of the images required for the project, for this I am using a bunch of status images available on wikimedia.
Place these in the src/main/resources/img directory.
The Macro
We will now create the macro class, this is called StatusLightMacro. You will notice from the source that the macro implements three
interfaces, Macro for the Macro itself and EditorImagePlaceholder and ResourceAware for rendering itself in the editor.
public class StatusLightMacro implements Macro, EditorImagePlaceholder, ResourceAware
{
private static final String PARAM_NAME = "percentage";
private static final String RESOURCE_DIR =
"/download/resources/com.atlassian.confluence.plugin.status-light/images/";
private static final Map<String, String> fileNames = new HashMap<String, String>();
static
{
fileNames.put("0%", "status_0.png");
fileNames.put("10%", "status_1.png");
fileNames.put("20%", "status_2.png");
fileNames.put("30%", "status_3.png");
fileNames.put("40%", "status_4.png");
fileNames.put("50%", "status_5.png");
fileNames.put("60%", "status_6.png");
fileNames.put("70%", "status_7.png");
fileNames.put("80%", "status_8.png");
fileNames.put("90%", "status_9.png");
fileNames.put("100%", "status_10.png");
}
private final SettingsManager settingsManager;
public StatusLightMacro(SettingsManager settingsManager)
{
this.settingsManager = settingsManager;
}
public String getImageLocation(Map<String, String> params, ConversionContext ctx)
{
if (params.containsKey(PARAM_NAME))
{
return RESOURCE_DIR + fileNames.get(params.get(PARAM_NAME));
}
return RESOURCE_DIR + fileNames.get("0%");
}
public String execute(Map<String, String> params, String defaultParam, ConversionContext ctx)
throws MacroExecutionException
{
return "<img src=\"" + settingsManager.getGlobalSettings().getBaseUrl() + "/" +
getImageLocation(params, ctx) + "\">";
}
public BodyType getBodyType()
{
return BodyType.NONE;
}
public OutputType getOutputType()
{
return OutputType.INLINE;
}
public String getResourcePath()
{
return null;
}
public void setResourcePath(String s) {}
}
With the macro created we will register it in the atlassian-plugin.xml file, notice how we have 11 parameters, one representing each
image we have in the project - this allows us to set a percentage as a parameter and have it render the correct image. We will also register
the images for the macro:
<xhtml-macro key="status-light" name="status-light"
class="com.atlassian.confluence.plugin.StatusLightMacro">
<description>Percentage based status lights</description>
<category name="admin"/>
<parameters>
<parameter name="percentage" type="enum">
<value name="0%"/>
<value name="10%"/>
<value name="20%"/>
<value name="30%"/>
<value name="40%"/>
<value name="50%"/>
<value name="60%"/>
<value name="70%"/>
<value name="80%"/>
<value name="90%"/>
<value name="100%"/>
</parameter>
</parameters>
</xhtml-macro>
<resource type="download" name="images/" location="img">
<param name="content-type" value="image/png"/>
</resource>
Extending the property panel
So far everything has been pretty standard, we have a macro that can render itself in the editor and a bunch of images to support it.
What we are going to do now is extend the macro definition to include the definition of a bunch of new buttons in the property panel, this
feature is new in Confluence 4.0-m17. The new button allows you to give it an id and a label that will be rendered, we will do this for all 11
states of the macro. Below is the new xhtml-macro block with the property panel buttons.
<xhtml-macro key="status-light" name="status-light"
class="com.atlassian.confluence.plugin.StatusLightMacro">
<description>Percentage based status lights</description>
<category name="admin"/>
<parameters>
<parameter name="percentage" type="enum">
<value name="0%"/>
<value name="10%"/>
<value name="20%"/>
<value name="30%"/>
<value name="40%"/>
<value name="50%"/>
<value name="60%"/>
<value name="70%"/>
<value name="80%"/>
<value name="90%"/>
<value name="100%"/>
</parameter>
</parameters>
<property-panel>
<button id="0" label="0%"/>
<button id="10" label="10%"/>
<button id="20" label="20%"/>
<button id="30" label="30%"/>
<button id="40" label="40%"/>
<button id="50" label="50%"/>
<button id="60" label="60%"/>
<button id="70" label="70%"/>
<button id="80" label="80%"/>
<button id="90" label="90%"/>
<button id="100" label="100%"/>
</property-panel>
</xhtml-macro>
Hooking it all up with some JavaScript
We now have the buttons definitions so we need some login to back them, here we do it with some javascript provided by the plugin - but first
of all we need to register this with the atlassian-plugin.xml file.
<web-resource name="Javascript" key="editor_status-light">
<resource type="download" name="status-light.js" location="js/status-light.js"/>
<context>editor</context>
</web-resource>
Notice that the context is set to editor, the events we register for in this javascript file will not make any sense outside of the editor.
Confluence now provides a mechanism for plugin developers to hook into the events of their custom buttons on the property panel, this
method is:
AJS.Confluence.PropertyPanel.Macro.registerButtonHandler(id, handler)
Where id is the id you have registered your button as in the atlassin-plugin.xml file and the handler is a function callback that will be
run when your button is clicked, this function gets passed the event object and the currently selected macro node. Below is a snippet of the
status-light.js provided in the source:
var updateMacro = function(macroNode, param) {
var $macroDiv = AJS.$(macroNode);
AJS.Rte.getEditor().selection.select($macroDiv[0]);
AJS.Rte.BookmarkManager.storeBookmark();
var macroRenderRequest = {
contentId: Confluence.Editor.getContentId(),
macro: {
name: "status-light",
params: {"percentage": param},
defaultParameterValue: "",
body : ""
}
};
tinymce.confluence.MacroUtils.insertMacro(macroRenderRequest);
};
AJS.Confluence.PropertyPanel.Macro.registerButtonHandler("0", function(e, macroNode) {
updateMacro(macroNode, "0%");
});
Notice that this snippet only handles the button with the id of 0, for brevity I have excluded the rest of the handlers (they change only in id
and percentage values). The function defined at the top, updateMacro is used to modify the macro parameters and redraw the macro in the
editor.
The finished macro
The macro we have created will render itself as an image placeholder within the editor:
Selecting the macro will display our extended property panel:
Resources
You can download the source from here: https://bitbucket.org/rthomas/confluence-status-light-macro/overview
Find out more about rendering images in the editor as macro placeholders: Providing an image as a macro placeholder in the editor
Find out more on Confluence 4.0: Preparing for Confluence 4.0
Preventing XSS issues with macros in Confluence 4.0
In Confluence 4.0, the new XHTML renderer no longer has a 'render mode' for escaping the bodies passed to the macros with a plain text
body, meaning that they will have to be escaped by the macro themselves.
You can use the HtmlEscaper static method escapeAll in order to escape the body of plain text, see the javadoc below for usage.
Replaces the HTML "special characters" <, >, ", ', and & with their equivalent entities in HTML 4 and returns the result. Also
replaces the Microsoft "smart quotes" characters (extended ASCII 145-148) with their equivalent HTML entities.
Passing true for preserveExistingEntities will try to not break existing entities already found in the input string, by avoiding
escaping ampersands which form part of an existing entity like <. Passing false will do the normal behaviour of escaping
everything.
@param s the String to escape
@param preserveExistingEntities if true, will avoid escaping the ampersand in an existing entity like <. If false, the
method will do a normal escaping by replace all matched characters.
@return the string with special characters replaced by entities.
You will note in the above javadoc for escapeAll that quote is referred to as a special character in HTML. This is not strictly true (see the
specification) yet the quote does require special handling in order to prevent XSS attacks. Using something like
org.apache.commons.lang.StringEscapeUtils#escapeHtml(String) instead of escapeAll will result in vulnerabilities as
discussed in Apache Foundation issue LANG-572.
Providing an image as a macro placeholder in the editor
Overview
As of Confluence 4.0-m17 a plugin developer can specify an image to use as a macro placeholder in the editor, rather than the placeholder
image itself. This is only applicable to macros without a body.
The image provided can come from anywhere within the current Confluence context, it can be a static image or dynamically generated by
your plugin.
In order to make use of this feature you must edit your plugin and implement the EditorImagePlaceholder interface.
If you macro has a body and this is used for that macro, the user will have no way of modifying the body of the macro.
For a complete example of how to use an image placeholder and how to use the pluggable macro property panels, see the
Confluence Status Macro, available on bitbucket: confluence-status-macro
Example
For this we will use a new cheese macro that renders a static image of cheese both in the editor and when it is executed.
package com.atlassian.confluence.plugin.cheese;
import ...
public class NewCheese implements Macro, EditorImagePlaceholder, ResourceAware
{
private static final String IMAGE_PATH =
"/download/resources/com.atlassian.confluence.plugin.cheese.cheese/cheese.jpg";
private final SettingsManager settings;
public NewCheese(SettingsManager settings)
{
this.settings = settings;
}
public ImagePlaceholder getImagePlaceholder(Map<String, String> params, ConversionContext ctx)
{
return new DefaultImagePlaceholder(IMAGE_PATH, new Dimensions(640, 480), false);
}
public String execute(Map<String, String> params, String body, ConversionContext ctx) throws
MacroExecutionException
{
return "<img src=\"" + settings.getGlobalSettings().getBaseUrl() + "/" +
getImageLocation(params, ctx) + "\">";
}
public BodyType getBodyType()
{
return BodyType.NONE;
}
public OutputType getOutputType()
{
return OutputType.BLOCK;
}
}
For this example we are just referencing a resource we are providing in this plugin, the cheese.jpg file:
<resource type="download" name="cheese.jpg" location="img/cheese.jpg">
<param name="content-type" value="image/jpeg"/>
</resource>
The String referenced in the ImagePlaceholder should be relative to the confluence base url, the editor will automatically make sure that
this points to the correct url. Absolute URLs are not supported for this feature.
The image rendered in the editor as the placeholder will behave just like a regular bodyless marco placeholder, the property panel will still
function correctly and the user will be able to edit it just like a macro. Any parameter changes in the macro browser will cause the image to
be reloaded - so that changes can be seen.
The ImagePlaceholder interface is described below
public interface ImagePlaceholder
{
/**
* Returns the url to the image to render, relative to the Confluence base url.
*
* @return The url relative to the Confluence base url.
*/
String getUrl();
/**
* Returns the dimensions that the image is to be rendered as. Returning null will
* render the image at its default size.
*
* @return An instance of {@link Dimensions} representing the image dimensions.
*/
Dimensions getDimensions();
/**
* Returns true if the image should have the macro placeholder chrome applied to it.
*
* @return True if placeholder chrome is to be applied.
*/
boolean applyPlaceholderChrome();
}
Upgrading and Migrating an Existing Confluence Macro to 4.0
See Preventing XSS issues with macros in Confluence 4.0 for information on how to prevent XSS issues with your
plain-text macro.
Overview
This tutorial will cover how to upgrade an existing 3.x macro to a Confluence 4.0 macro, including migration.
The following concepts will be covered
1. The conditions that will lead to a 3.x macro getting migrated.
2. Having a 3.x and a 4.0 macro co-existing.
3. Using a custom migrator to migrate a macro.
Some Confluence plugin development knowledge is assumed.
1.
2.
3.
4.
Creating a plugin using the Atlassian Plugin SDK.
Installation / testing / debugging of plugins.
Basic knowledge of the Confluence object model.
Creating a new Confluence 4.0 Macro.
Prerequisites
1. A 3.x Confluence macro (or the one we will be using below).
2. If you are using maven2 for your dependency management, then update the confluence.version in your pom to reflect
Confluence 4.0:
<properties>
<confluence.version>4.0-m17</confluence.version>
<confluence.data.version>3.2</confluence.data.version>
</properties>
Macros we will be migrating
For the purpose of this tutorial we will be migrating two Confluence 3.x macros (listed below):
Macro
Details
{
mycheese
}
This is our version of the {cheese} macro - it has no body and takes no parameters, the output is the string: "I really like
cheese"
{
mycolour
}
This is our version of the {color} macro, it is bodied and takes a parameter (the colour) in the body of the macro. e.g. {
mycolour}red:This is red text{mycolour}
Module Descriptors for these macros
These macros will be setup using the following atlassian-plugin.xml:
atlassian-plugin.xml
<macro key="mycheese"
name="mycheese"
class="com.atlassian.confluence.plugin.xhtml.MyCheeseMacro">
<category name="development"/>
<parameters/>
</macro>
<macro key="mycolour"
name="mycolour"
class="com.atlassian.confluence.plugin.xhtml.MyColourMacro">
<category name="development"/>
<parameters/>
</macro>
Macro Source
MyCheeseMacro.java
package com.atlassian.confluence.plugin.xhtml;
import
import
import
import
com.atlassian.renderer.RenderContext;
com.atlassian.renderer.v2.RenderMode;
com.atlassian.renderer.v2.macro.BaseMacro;
com.atlassian.renderer.v2.macro.MacroException;
import java.util.Map;
public class MyCheeseMacro extends BaseMacro
{
@Override
public boolean hasBody()
{
return false;
}
@Override
public RenderMode getBodyRenderMode()
{
return RenderMode.NO_RENDER;
}
@Override
public String execute(Map parameters, String body, RenderContext renderContext) throws
MacroException
{
return "I <i>really</i> like cheese!";
}
}
MyColourMacro.java
package com.atlassian.confluence.plugin.xhtml;
import
import
import
import
import
com.atlassian.renderer.RenderContext;
com.atlassian.renderer.v2.RenderMode;
com.atlassian.renderer.v2.macro.BaseMacro;
com.atlassian.renderer.v2.macro.MacroException;
org.apache.commons.lang.StringUtils;
import java.text.MessageFormat;
import java.util.Map;
public class MyColourMacro extends BaseMacro
{
public static final String COLOUR_PARAM = "colour";
@Override
public boolean hasBody()
{
return true;
}
@Override
public RenderMode getBodyRenderMode()
{
return RenderMode.NO_RENDER;
}
@Override
public String execute(Map parameters, String body, RenderContext renderContext) throws
MacroException
{
if (StringUtils.isBlank(body))
{
return "";
}
String[] bodyItems = StringUtils.split(body, ":", 2);
if (bodyItems.length != 2)
{
return body;
}
return formatString(bodyItems[0], bodyItems[1]);
}
public String formatString(String colour, String body)
{
return MessageFormat.format("<span style=\"color: {0};\">{1}</span>", colour, body);
}
}
Conditions for when a macro will be migrated
Now that we have the macros that we will be using for this tutorial covered, we will now cover the conditions for when a macro will get
migrated:
1. Does the macro have an XHTML (4.0) implementation available?
a. If yes then we will migrate it either with the automatic migration or with a custom migrator.
2. Does the macro have a body?
a. If no then it will be migrated.
3. Otherwise we will wrap it in the unmigrated-wiki-markup macro.
In order for the macro to show up in the Macro Browser, it will need to supply the correct metadata - this is for both 3.x and
4.0 macros. More information can be found here: Including Information in your Macro for the Macro Browser
This flow chart should make things a bit simpler:
What if a macro is not migrated?
If a macro is not migrated then the macro will not appear in the Macro Browser, nor will it appear in the autocomplete. Also if the macro is
inserted through the Insert Wiki Markup dialog the macro will be wrapped with the unmigrated-wiki-markup macro.
Migrating our macros
Now if we look at the macros we have above, we can see that according to the flowchart the {mycheese} macro will get migrated as it does
not have a body, however the {mycolour} macro will not, we will now cover what needs to be done to get the {mycolour} macro to migrate.
In order for us to get the mycolour macro to migrate we will need to provide an XHTML implementation of that macro and an appropriate
module descriptor, we can implement the new Macro interface in the same macro class, which is what we will do here:
MyColourMacro.java (4.0)
package com.atlassian.confluence.plugin.xhtml;
import
import
import
import
import
import
import
import
com.atlassian.confluence.content.render.xhtml.ConversionContext;
com.atlassian.confluence.macro.Macro;
com.atlassian.confluence.macro.MacroExecutionException;
com.atlassian.renderer.RenderContext;
com.atlassian.renderer.v2.RenderMode;
com.atlassian.renderer.v2.macro.BaseMacro;
com.atlassian.renderer.v2.macro.MacroException;
org.apache.commons.lang.StringUtils;
import java.text.MessageFormat;
import java.util.Map;
public class MyColourMacro extends BaseMacro implements Macro
{
public static final String COLOUR_PARAM = "colour";
@Override
public boolean hasBody()
{
return true;
}
@Override
public RenderMode getBodyRenderMode()
{
return RenderMode.NO_RENDER;
}
@Override
public String execute(Map parameters, String body, RenderContext renderContext) throws
MacroException
{
if (StringUtils.isBlank(body))
{
return "";
}
String[] bodyItems = StringUtils.split(body, ":", 2);
if (bodyItems.length != 2)
{
return body;
}
return formatString(bodyItems[0], bodyItems[1]);
}
public String formatString(String colour, String body)
{
return MessageFormat.format("<span style=\"color: {0};\">{1}</span>", colour, body);
}
@Override
public String execute(Map<String, String> params, String body, ConversionContext
conversionContext) throws MacroExecutionException
{
try
{
return execute(params, body, (RenderContext) null);
}
catch (MacroException e)
{
throw new MacroExecutionException(e);
}
}
@Override
public BodyType getBodyType()
{
return BodyType.PLAIN_TEXT;
}
@Override
public OutputType getOutputType()
{
return OutputType.BLOCK;
}
}
As you can see the new execute(...) method delegates to the old one, we are using the same functionality as the 3.x macro for our 4.0
macro.
Once the macro is implemented we need to specify a new module descriptor for it - xhtml-macro.
atlassian-plugin.xml
<xhtml-macro key="mycolour-xhtml"
name="mycolour"
class="com.atlassian.confluence.plugin.xhtml.MyColourMacro">
<category name="development"/>
<parameters/>
</xhtml-macro>
This looks much the same as the 3.x macro at the moment, the only difference is the new module descriptor name: xhtml-macro.
Now that we have the XHTML implementation of it we will be able to see it in the Macro Browser and in autocomplete, it also means that the
macro will have it's own placeholder rather than the unmigrated-wiki-markup placeholder.
Custom migrators
A custom migrator can be specified by a plugin in order to migrate a specified macro. To do this one must first implement the Migrator
interface and then define the migrator as a module in the atlassian-plugin.xml.
MacroMigration.java
public interface MacroMigration
{
/**
* Migrates a wiki-markup representation of a macro to XHTML
* @param macro The {@link com.atlassian.confluence.xhtml.api.MacroDefinition} is wiki-markup
form.
* @param context The {@link com.atlassian.confluence.content.render.xhtml.ConversionContext}
to perform the migration under.
* @return An XHTML representation of the macro.
*/
MacroDefinition migrate(MacroDefinition macro, ConversionContext context);
}
Using a custom migrator to remove the parameter from our mycolour macro.
In this section we will implement a migrator to remove the parameter from the body of our mycolour macro and insert it as a parameter, this
will occur whenever a wiki-markup version of this macro is encountered (either at initial migration time or by using the Insert Wiki Markup
dialog).
In order to do this we will first update the execute(...) method of our macro to take a parameter:
New execute(...) method for parameters)
@Override
public String execute(Map<String, String> params, String body, ConversionContext
conversionContext) throws MacroExecutionException
{
if (!params.containsKey(COLOUR_PARAM))
{
return body;
}
String colour = params.get(COLOUR_PARAM);
return formatString(colour, body);
}
We will also update the module descriptor for this macro in order to support parameters in the Macro Browser.
New xhtml-macro module descriptor with parameter information
<xhtml-macro key="mycolour-xhtml"
name="mycolour"
class="com.atlassian.confluence.plugin.xhtml.MyColourMacro">
<category name="development"/>
<parameters>
<parameter name="colour" type="enum">
<value name="red"/>
<value name="green"/>
<value name="blue"/>
<value name="pink"/>
<value name="black"/>
</parameter>
</parameters>
</xhtml-macro>
Now that the macro is setup to accept a parameter we will implement the Migrator interface, as you can see the migrator uses simular
logic (to the 3.x macro) to extract the parameter and insert it into the macro definition. The MacroDefinition returned from this method will
replace the one read in.
MyColourMacroMigrator.java
package com.atlassian.confluence.plugin.xhtml;
import
import
import
import
import
import
com.atlassian.confluence.content.render.xhtml.ConversionContext;
com.atlassian.confluence.content.render.xhtml.definition.MacroBody;
com.atlassian.confluence.content.render.xhtml.definition.PlainTextMacroBody;
com.atlassian.confluence.macro.xhtml.MacroMigration;
com.atlassian.confluence.xhtml.api.MacroDefinition;
org.apache.commons.lang.StringUtils;
import java.util.HashMap;
import java.util.Map;
public class MyColourMacroMigrator implements MacroMigration
{
@Override
public MacroDefinition migrate(MacroDefinition macroDefinition, ConversionContext
conversionContext)
{
MacroBody macroBody = macroDefinition.getBody();
if (StringUtils.isBlank(macroBody.getBody()))
{
return macroDefinition;
}
final String[] bodyItems = StringUtils.split(macroBody.getBody(), ":", 2);
if (bodyItems.length != 2)
{
return macroDefinition;
}
Map<String, String> params = new HashMap<String, String>(1)
{{
put(MyColourMacro.COLOUR_PARAM, bodyItems[0]);
}};
macroDefinition.setParameters(params);
MacroBody newBody = new PlainTextMacroBody(bodyItems[1]);
macroDefinition.setBody(newBody);
return macroDefinition;
}
}
Now that we have the Migrator defined we will need to define the module in the atlassian-plugin.xml file, the macro-migrator
module descriptor takes three parameter; the key, the macro-name and the class:
atlassian-plugin.xml macro-migrator module descriptor
<macro-migrator key="mycolour-migrator"
macro-name="mycolour"
class="com.atlassian.confluence.plugin.xhtml.MyColourMacroMigrator"/>
Macro Aliases
If you have multiple aliases defined for a macro you should ensure you add a macro-migrator entry for each alias. For
example <xhtml-macro key="key" name="proper-name" class="my.proper.Macro">
...
</xhtml-macro>
<macro-migrator key="migration-key" macro-name="proper-name" class="my.Migration" />
<xhtml-macro key="alias1-key" name="alias1" class="my.first.Alias">
...
</xhtml-macro>
<macro-migrator key="alias1-migration-key" macro-name="alias1" class="my.Migration"
/>
You might also want to consider if the 4.0 migration is a suitable time to migrate away any of these presumably historic
aliases. You can write a MacroMigration to change the name of the macro from that of the alias to the proper name.
Conclusion
In this tutorial you saw how macro migration will occur for 3.x macros, how to implement a 4.0 macro and have it co-exist with a 3.x macro
and how to implement a custom macro migrator.
Related Content
No content found for label(s) conf4_dev.
Plugin Development Upgrade FAQ for 4.0
This page provides a resource for plugin developers who are migrating their plugins to Confluence 4.0. It explains what happens to
incompatible plugins, how to identify how Confluence is handling a given plugin and links to resources on how to update your plugin code.
Frequently Asked Questions
What will Confluence 4.0 do on upgrade from 3.x?
How does Confluence determine which macros are compatible with Confluence 4.0?
How does Confluence 4.0 handle plugins that are incompatible with Confluence 4.0?
What will happen if I don't update my plugin for Confluence 4.0?
How can I make my plugin Confluence 4.0 compatible?
What API changes have occurred in Confluence 4.0?
Other Confluence development resources
Frequently Asked Questions
What will Confluence 4.0 do on upgrade from 3.x?
Confluence 4.0 should publish most existing 3.5.x plugins with no changes required. When a plugin is not compatible with Confluence 4.0, it
will gracefully attempt to provide a way for the plugin to work seamlessly. If that fails, the information from references listed on this page can
be used to update the plugin code to make it 4.0 compatible.
How does Confluence determine which macros are compatible with Confluence 4.0?
On upgrading to Confluence 4.0, the Wiki Markup of all pages is parsed. This will detect the following occurences in the page content and
wrap each one in a Wiki Markup block:
Invalid Wiki Markup. (i.e. any page content that displays an error when the page is viewed).
A macro that has been disabled. (i.e. not installed, or removed from the Macro Browser).
A bodied macro that does not have a 4.0 compatible version (A 3.x version of the macro is installed, but there is no 4.0 version
installed).
In order for a macro to be migrated, both a 3.x and 4.0 version have to exist in the system (noted in each plugin's Atlassian plugin XML
file).
How does Confluence 4.0 handle plugins that are incompatible with Confluence 4.0?
If the first macro on a Confluence page isn't 4.0 compatible, it will wrap the entire 'scope' or 'level' of that macro in the Wiki Markup macro.
This is the intended outcome, the reason for this is due to ambiguity around detecting whether the macro has an ending tag or not.
In this case, the only real solution is to update your plugin to be compatible with Confluence 4.0. See the following section for specific
resources.
What will happen if I don't update my plugin for Confluence 4.0?
The majority of plugins will continue to work with 4.0, but not updating your plugin has the following effects:
The page should continue to render correctly.
The macro will not show up in the macro browser.
The macro will render as a Wiki markup bodied placeholder in the editor (for migrated usages).
How can I make my plugin Confluence 4.0 compatible?
See Upgrading and Migrating an Existing Confluence Macro to 4.0.
What API changes have occurred in Confluence 4.0?
The following Java methods have been removed from the Confluence 4.0 API:
ContentEntityObject.setContent(String)
ContentEntityObject.getContent()
If your macro (or plugin) makes use of these calls, it will fail. The failure case will only occur when a plugin built against 3.x is used in a 4.0
system – building the plugin against 4.0 will result in compile-time errors.
Other Confluence development resources
Creating a new Confluence 4.0 Macro
Extending the macro property panel - an example
Preventing XSS issues with macros in Confluence 4.0
Providing an image as a macro placeholder in the editor
Upgrading and Migrating an Existing Confluence Macro to 4.0
Plugin points for the editor in 4.0
Adding to the more format menu
You can add to this formatting menu by adding a web-item with section system.editor.more.formats to your plugin xml. For example:
<web-item key="editor-awesome-format" name="Awesome Format" section="system.editor.more.formats"
weight="10">
<label key="my.plugin.awesome.format"/>
<link linkId="my-awesome-format"/>
</web-item>
This will add an extra item after the first group of options (after the monospace formatting option). You can then bind to this button in your
javascript like so:
$("#my-awesome-button").click(function(){
tinymce.activeEditor.execCommand("FormatBlock", false, "samp");
});
You can write your own TinyMCE commands or simply execute an existing one. See the TinyMCE documentation for more details.
Adding your macro to the insert menu
You can add your macro to the insert menu by adding a web-item with section system.editor.featured.macros.default to your
plugin xml. For example:
<web-item key="editor-my-macro" name="Insert Menu Link - My Macro"
section="system.editor.featured.macros.default" weight="100">
<label key="my.plugin.macro"/>
<link linkId="mymacro"/>
</web-item>
This will add an extra item to the second group of options in the menu, depending on the weight of the web item. The linkId must be the key
of the macro you have defined in your xml. When your new menu option is clicked, it will automatically open the macro browser up with the
insert macro page of your macro.