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 (devrel@atlassian.com) 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 confluence4@atlassian.com. 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.