JIRA 5.x Development Cookbook


JIRA 5.x Development Cookbook This book is your one-stop resource for mastering JIRA extensions and customizations Jobin Kuruvilla BIRMINGHAM - MUMBAI JIRA 5.x Development Cookbook Copyright © 2013 Packt Publishing All rights reserved. No part of this book may be reproduced, stored in a retrieval system, or transmitted in any form or by any means, without the prior written permission of the publisher, except in the case of brief quotations embedded in critical articles or reviews. Every effort has been made in the preparation of this book to ensure the accuracy of the information presented. However, the information contained in this book is sold without warranty, either express or implied. Neither the author, nor Packt Publishing, and its dealers and distributors will be held liable for any damages caused or alleged to be caused directly or indirectly by this book. Packt Publishing has endeavored to provide trademark information about all of the companies and products mentioned in this book by the appropriate use of capitals. However, Packt Publishing cannot guarantee the accuracy of this information. First published: April 2013 Production Reference: 1180413 Published by Packt Publishing Ltd. Livery Place 35 Livery Street Birmingham B3 2PB, UK. ISBN 978-1-78216-908-6 www.packtpub.com Cover Image by Suresh Mogre (suresh.mogre.99@gmail.com) Credits Author Jobin Kuruvilla Reviewers Felix Martineau Mizan Ali Sayed Acquisition Editor Kevin Colaco Lead Technical Editor Sruthi Kutty Technical Editors Dominic Pereira Kirti Pujari Mameet Singh Vasir Hardik B. Soni Project Coordinator Hardik Patel Proofreaders Maria Gould Paul Hindle Aaron Nash Indexer Rekha Nair Graphics Abhinash Sahu Production Coordinator Arvindkumar Gupta Cover Work Arvindkumar Gupta About the Author Jobin Kuruvilla is an Atlassian Consultant with experience in customizing JIRA and writing JIRA plugins for various customers. He is currently working with Go2Group as an Atlassian platinum expert, and is involved in managing Atlassian products for big enterprises as well as small starter license installations. Jobin is the author of JIRA Development Cookbook, Packt Publishing, released in 2011, which is a well-received book in the JIRA community. He also runs a website named J-Tricks (http://www.j-tricks.com), using which he shares numerous tutorials to help the developer community, who he believes have contributed immensely to his personal development. It is indeed those tutorials that sowed the first seeds of JIRA Development Cookbook. Jobin started his career as a Java/J2EE developer in one of the biggest IT companies in India. After spending his initial years in the SOA world, he got hooked into this amazing product called JIRA, which he came across during the evaluation of a number of third-party tools. Soon, Jobin realized the power of JIRA, and pledged to spread the word. He has been doing it ever since, and he reckons there is a long way to go! Acknowledgement No book is the product of just the author; he just happens to be the one with his name on the cover. A number of people contributed to the success of this book, and it would take more space than I have to thank each one individually. First of all, thanks to the Almighty God for helping me to sail through the difficulties in this short life and for making my life as wonderful as it is now. The next biggest thanks goes to Project Coordinator, Hardik Patel, and Lead Technical Editor, Sruthi Kutty, both of whom went through the pain of making me write another book. Also, thanks to the entire Packt Publishing team for working so diligently to help bring out another high quality product. It is amazing to work with a team of talented developers and technical geeks. Thank you STORM team, PD&B team, and RAMP team. Your encouragement and support were invaluable to me; you guys rock! I must also thank the talented JIRA community who are instrumental in helping each other, sharing solutions, being active in the forums, running user groups, and what not? I am just one of the many who have benefited. Before I wind up, thank you Atlassian for giving us JIRA and a set of other wonderful products. You don't realize how much you are making our lives easier! Last, but not the least, a big thanks to all at Go2group for the support extended in writing this book and believing in my capabilities. About the Reviewers Felix Martineau runs the Atlassian practice at TechSolCom (http://www.techsolcom.ca) and has been working as a professional JIRA consultant since 2008. Having started with JIRA in 2007, over the years he has worked with the entire Atlassian portfolio. Felix has a rare blend of technical expertise, people skills, and business acumen. For Felix, the people are always more important than the tools. I want to thank the people at Packt Publishing for giving me this opportunity to review the book. Jobin Kuruvilla is one of the top people in the JIRA community, so it's an honor for me to be involved. I would also like to thank my girlfriend Genevieve for her continuous support, as well as Paul Gamache and Jean Perron at TechSolCom, to whom I owe a lot professionally. Mizan Ali Sayed is a Master in Computer Science from Pune university, India. Mizan currently works as an Atlassian tools specialist and has experience with implementing, customizing, and supporting large scale enterprise JIRA. He is active within the Atlassian forum "Answers" and has published open source add-ons on the Atlassian Marketplace. I would like to thank my parents and friends for their continuous support and encouragement. www.PacktPub.com Support files, eBooks, discount offers and more You might want to visit www.PacktPub.com for support files and downloads related to your book. Did you know that Packt offers eBook versions of every book published, with PDF and ePub files available? You can upgrade to the eBook version at www.PacktPub.com and as a print book customer, you are entitled to a discount on the eBook copy. Get in touch with us at service@ packtpub.com for more details. At www.PacktPub.com, you can also read a collection of free technical articles, sign up for a range of free newsletters and receive exclusive discounts and offers on Packt books and eBooks. http://PacktLib.PacktPub.com Do you need instant solutions to your IT questions? PacktLib is Packt's online digital book library. Here, you can access, read and search across Packt's entire library of books. Why Subscribe? ff Fully searchable across every book published by Packt ff Copy and paste, print and bookmark content ff On demand and accessible via web browser Free Access for Packt account holders If you have an account with Packt at www.PacktPub.com, you can use this to access PacktLib today and view nine entirely free books. Simply use your login credentials for immediate access. Instant Updates on New Packt Books Get notified! Find out when new books are published by following @PacktEnterprise on Twitter, or the Packt Enterprise Facebook page. This book is dedicated to Sarah, Anna, Anu, Juby, Alice, and Kuruvilla. Also, my friends from TKM and JNV Kottayam. This book would not have been possible without you, one way or the other! A big thank you from the bottom of my heart. I have nothing to give back, but my love and prayers. Table of Contents Preface 1 Chapter 1: Plugin Development Process 7 Introduction 7 Setting up the development environment 10 Creating a skeleton plugin 13 Adding plugin modules 17 Deploying a JIRA plugin 19 Making changes to and redeploying a plugin 23 Using FastDev for plugin development 24 Testing and debugging 28 Chapter 2: Understanding the Plugin Framework 31 Introduction 31 Architecture explained 34 Types of plugin modules 39 Working with the Plugins1 and Plugins2 versions 44 JIRA system plugins 46 Stable and core APIs 49 Modifying Atlassian bundled plugins 50 Converting plugins from v1 to v2 51 Adding resources into plugins 54 Adding web resources into plugins 56 Building JIRA from source 59 Adding new webwork actions to JIRA 63 Extending a webwork action in JIRA 68 Capturing plugin installation/uninstallation events 71 ii Table of Contents Chapter 3: Working with Custom Fields 77 Introduction 78 Writing a simple custom field 78 Custom field searchers 83 Dealing with custom fields on an issue 88 Programming custom field options 90 Overriding validation of custom fields 93 Customizing the change log value 94 Migrating from one custom field type to another 96 Making custom fields sortable 99 Displaying custom fields on subtask columns 100 User and date fields 102 Adding custom fields to notification mails 103 Adding help text for a custom field 105 Removing the "none" option from a select field 107 Making the custom field project importable 108 Changing the size of a text area custom field 109 Chapter 4: Programming Workflows 113 Introduction 114 Writing a workflow condition 115 Writing a workflow validator 122 Writing a workflow post function 129 Editing an active workflow 136 Making an issue editable/non-editable based on workflow status 138 Including/excluding resolutions for specific transitions 139 Permissions based on workflow status 140 Internationalization in workflow transitions 142 Obtaining available workflow actions programmatically 144 Programmatically progressing on workflows 146 Obtaining workflow history from the database 148 Reordering workflow actions in JIRA 151 Creating common transitions in workflows 153 Creating global transitions in workflows 158 Jelly escalation 160 Chapter 5: Gadgets and Reporting in JIRA 165 Introduction 166 Writing a JIRA report 167 Reports in an Excel format 174 Data validation in JIRA reports 177 Restricting access to reports 179 iii Table of Contents Object-configurable parameters for reports 181 Creating a pie chart in JIRA 189 Writing JIRA 4 gadgets 194 Invoking REST services from gadgets 202 Configuring user preferences in gadgets 206 Accessing gadgets outside of JIRA 214 Chapter 6: The Power of JIRA Searching 219 Introduction 219 Writing a JQL function 220 Sanitizing JQL functions 226 Adding a search request view 229 Smart querying using quick search 235 Searching in plugins 239 Parsing JQL queries in plugins 242 Linking directly to search queries 243 Index and de-index programmatically 244 Managing filters programmatically 246 Subscribing to a filter 249 Chapter 7: Programming Issues 253 Introduction 253 Creating an issue from a plugin 254 Creating subtasks on an issue 257 Updating an issue 258 Deleting an issue 259 Adding new issue operations 260 Conditions on issue operations 263 Working with attachments 265 Time tracking and worklog management 267 Working with comments on issues 275 Programming change logs 277 Programming issue links 279 Discarding fields while cloning 282 JavaScript tricks on issue fields 284 Creating issues and comments from an e-mail 287 Chapter 8: Customizing the UI 291 Introduction 292 Changing the basic look and feel 292 Adding new web sections in the UI 295 Adding new web items in the UI 297 Use of decorators 300 iv Table of Contents Adding conditions for web fragments 305 Creating new Velocity context for web fragments 308 Adding a new drop-down menu on the top navigation bar 310 Dynamic creation of web items 311 Adding new tabs in the View Issue screen 315 Adding new tabs in the Browse Project screen 319 Creating the project tab panel using fragments 321 Adding new tabs in the Browse Version screen 325 Adding new tabs in the Browse Component screen 328 Adding issue link renderers 330 Extending a webwork action to add UI elements 338 Displaying dynamic notifications/warnings on issues 342 Re-ordering issue operations in the View Issue page 346 Re-ordering fields in the View Issue page 348 Chapter 9: Remote Access to JIRA 353 Introduction 354 Writing a Java client for the REST API 354 Creating a SOAP client 356 Writing a Java XML-RPC client 358 Working with issues 360 Working with attachments 364 Remote time tracking 366 Working with comments 368 Remote user and group management 369 Progressing an issue in a workflow 370 Managing versions 371 Managing components 373 Remote administration methods 374 Exposing services and data entities as REST APIs 378 Deploying a SOAP service in JIRA 384 Deploying an XML-RPC service within JIRA 388 Chapter 10: Dealing with the JIRA Database 393 Introduction 394 Extending the JIRA database with a custom schema 395 Accessing database entities from plugins 399 Persisting plugin information in the JIRA database 401 Using Active Objects to store data 405 Accessing the JIRA configuration properties 408 Getting a database connection for JDBC calls 409 Migrating a custom field from one type to another 410 v Table of Contents Retrieving issue information from a database 411 Retrieving custom field details from a database 414 Retrieving permissions on issues from a database 416 Retrieving workflow details from a database 419 Updating the issue status in a database 421 Retrieving users and groups from a database 422 Dealing with change history in a database 424 Chapter 11: Useful Recipes 427 Introduction 428 Writing a service in JIRA 428 Adding configurable parameters to a service 430 Writing scheduled tasks in JIRA 433 Writing listeners in JIRA 435 Customizing e-mail content 442 Redirecting to a different page in webwork actions 444 Adding custom behavior for user details 445 Deploying a servlet in JIRA 450 Adding shared parameters to the servlet context 453 Writing a servlet context listener 455 Using filters to intercept queries in JIRA 456 Adding and importing components in JIRA 459 Adding new module types to JIRA 463 Enabling access logs in JIRA 467 Enabling SQL logging in JIRA 470 Overriding JIRA's default components in plugins 471 Internationalization in webwork plugins 474 Sharing common libraries across v2 plugins 476 Operations using direct HTML links 478 Index 483 Preface This book is your one-stop resource for mastering JIRA extension and customization. You will learn how to create your own JIRA plugins, customize the look and feel of your JIRA UI, work with workflows, issues, custom fields, and much more. The book starts with recipes on simplifying the plugin development process followed by a complete chapter dedicated to the plugin framework for mastering plugins in JIRA. Then we will move on to writing custom field plugins to create new field types or custom searchers. We then learn how to program and customize workflows to transform JIRA into a user-friendly system. We will then look at customizing the various searching aspects of JIRA such as JQL, searching in plugins, managing filters, and so on. Then the book steers towards programming issues, that is, creating/editing/deleting issues, creating new issue operations, managing the various other operations available on issues using the JIRA APIs, and so on. In the latter half of the book, you will learn how to customize JIRA by adding new tabs, menus, and web items, and communicate with JIRA using the REST, SOAP, or XML/RPC interfaces, as well as working with the JIRA database. The book ends with a chapter on useful and general JIRA recipes. What this book covers Chapter 1, Plugin Development Process, covers the fundamentals of the JIRA plugin development process. It covers, in detail, the setting up of a development environment, creating a plugin, deploying it, and testing it. Chapter 2, Understanding the Plugin Framework, covers, in detail, the JIRA architecture, and also looks at the various plugin points. It also looks at how to build JIRA from source and extend or override the existing JIRA functionalities. Preface 2 Chapter 3, Working with Custom Fields, looks at programmatically creating custom fields in JIRA, writing custom field searchers, and various other useful recipes related to custom fields. Chapter 4, Programming Workflows, looks at the various ways of programming the JIRA workflows. It includes writing new conditions, validators, post functions, and so on, and contains related recipes that are useful in extending the workflows. Chapter 5, Gadgets and Reporting in JIRA, covers the reporting capabilities of JIRA. It looks at writing reports, dashboard gadgets, and much more in detail. Chapter 6, The Power of JIRA Searching, covers the searching capabilities of JIRA and how it can be extended using the JIRA APIs. Chapter 7, Programming Issues, looks at the various APIs and methods used for managing issues programmatically. It covers the CRUD operations, working with attachments, programming the change logs and issue links, time tracking, and much more. Chapter 8, Customizing the UI, looks at the various ways of extending and modifying the JIRA user interface. Chapter 9, Remote Access to JIRA, looks at the remote capabilities of JIRA—REST, SOAP, and XML/RPC—and the ways of extending them. Chapter 10, Dealing with the JIRA Database, looks at the database architecture of JIRA and covers the major tables in detail. It also covers the different ways to extend the storage and access or modify the data from plugins. Chapter 11, Useful Recipes, covers a selected list of useful recipes, which do not belong in the preceding categories, but are powerful enough to get your attention. Read away! What you need for this book This book focuses on JIRA development. You need the following software as a bare minimum: ff JIRA 5.x+ ff JAVA 1.6+ ff Maven 2.x ff Atlassian Plugin SDK ff An IDE of your choice. The examples in the book use Eclipse and SQL Developer. Some of the recipes are too simple to use the fully-fledged plugin development process, and you will see this highlighted as you read through the book! Preface 3 Who this book is for If you are a JIRA developer or a project manager who wants to fully exploit the exciting capabilities of JIRA, then this is the perfect book for you. Conventions In this book, you will find a number of styles of text that distinguish between different kinds of information. Here are some examples of these styles, and an explanation of their meaning. Code words in text, database table names, folder names, filenames, file extensions, pathnames, dummy URLs, user input, and Twitter handles are shown as follows: " To use this, edit the settings.xml file under M2_HOME/conf/ by modifying the localRepository attribute to point to the embedded repository folder." A block of code is set as follows: . true http proxy.demo.com 8080 demouser demopassword localhost|*.demosite.com . Any command-line input or output is written as follows: select id from changegroup where issueid = '10010' New terms and important words are shown in bold. Words that you see on the screen, in menus or dialog boxes for example, appear in the text like this: "That is, you can just import the project using the File | Import | Existing Maven Projects option, and selecting the relevant project." Preface 4 Warnings or important notes appear in a box like this. Tips and tricks appear like this. Reader feedback Feedback from our readers is always welcome. Let us know what you think about this book—what you liked or may have disliked. Reader feedback is important for us to develop titles that you really get the most out of. To send us general feedback, simply send an e-mail to feedback@packtpub.com, and mention the book title via the subject of your message. If there is a topic that you have expertise in and you are interested in either writing or contributing to a book, see our author guide on www.packtpub.com/authors. Customer support Now that you are the proud owner of a Packt book, we have a number of things to help you to get the most from your purchase. Downloading the example code You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you. Errata Although we have taken every care to ensure the accuracy of our content, mistakes do happen. If you find a mistake in one of our books—maybe a mistake in the text or the code— we would be grateful if you would report this to us. By doing so, you can save other readers from frustration and help us improve subsequent versions of this book. If you find any errata, please report them by visiting http://www.packtpub.com/submit-errata, selecting your book, clicking on the errata submission form link, and entering the details of your errata. Once your errata are verified, your submission will be accepted and the errata will be uploaded on our website, or added to any list of existing errata, under the Errata section of that title. Any existing errata can be viewed by selecting your title from http://www.packtpub.com/support. Preface 5 Piracy Piracy of copyright material on the Internet is an ongoing problem across all media. At Packt, we take the protection of our copyright and licenses very seriously. If you come across any illegal copies of our works, in any form, on the Internet, please provide us with the location address or website name immediately so that we can pursue a remedy. Please contact us at copyright@packtpub.com with a link to the suspected pirated material. We appreciate your help in protecting our authors, and our ability to bring you valuable content. Questions You can contact us at questions@packtpub.com if you are having a problem with any aspect of the book, and we will do our best to address it. 1 Plugin Development Process In this chapter, we will cover: ff Setting up the development environment ff Creating a skeleton plugin ff Adding plugin modules ff Deploying a JIRA plugin ff Making changes to and redeploying a plugin ff Using FastDev for plugin development ff Testing and debugging Introduction Atlassian JIRA, as we all know, is primarily an issue tracking and project tracking system. What many people do not know, though, is the power of its numerous customization capabilities, using which we can turn it into a different system altogether! Maybe a help desk system, a user story management system, an online approval process, and a lot more. This is in addition to the issue tracking and project tracking capabilities for which JIRA, arguably, is the best player in the market. So what are these customizations? How can we convert the JIRA we know into a product we want? Or maybe just add extra functionalities that are specific to our organization? The answer to these questions probably can be summarized in a single word: plugins. JIRA has given the power to its users to write plugins and customize the functionality in a way they find suitable. Plugin Development Process 8 But is that the only way? Definitely not! JIRA itself provides a lot of customization options through its user interface, and in more demanding cases, using property files such as jira-config.properties. In some cases, you will also find yourself modifying some of the JIRA core files to tweak functionality or to work around a problem. We will see more of that in the chapters to come, but the best entry point to JIRA customizations is through plugins, and that is where we start our cookbook before we move on to the in-depth details. What is a JIRA plugin? So, what is a JIRA plugin? JIRA itself is a web application written in Java. That doesn't mean you need to know Java to write a plugin, though in most cases you will need to. You might end up writing a simple descriptor file to add a few links here and there. If that makes the "non-Java" developer in you happy, watch out for the different plugin modules JIRA supports. A JIRA plugin is a JAR file that has a mandatory plugin descriptor and some optional Java classes and Velocity templates. The Velocity templates are used to render the HTML pages associated with your plugin, but in some cases, you might also want to introduce JSPs to make use of some pre-existing templates in JIRA. JSPs, as opposed to Velocity templates, cannot be embedded in the plugin, but instead they should be dropped into the appropriate folders in the JIRA web application. Using Velocity templates is therefore recommended over JSPs. You can find more details on writing Velocity templates at http://velocity.apache.org/engine/releases/ velocity-1.5/user-guide.html#velocity_ template_language_vtl:_an_introduction. The plugin descriptor, the only mandatory part of a plugin, is an XML file that must be named atlassian-plugin.xml. This file is located at the root of the plugin. The atlassian-plugin.xml file defines the various modules in a plugin. The different types of available plugin modules include reports, custom field types, and so on. These are discussed in detail in the next chapter. The plugin development process The process of developing a JIRA plugin can be of varying complexity depending on the functionality we are trying to achieve. The plugin development process essentially is a four-step process: ff Developing the plugin ff Deploying it into local JIRA ff Testing the plugin functionality ff Making changes and redeploying the plugin if required Each of these steps is explained in detail through the various recipes in this book! Chapter 1 9 JIRA, on start up, identifies all the plugins that are deployed in the current installation. You can deploy multiple plugins, but there are some things you need to keep an eye on! The atlassian-plugin.xml file has a plugin key, which should be unique across all the plugins. It is much similar to a Java package. Each module in the plugin also has a key that is unique within the plugin. The plugin key combined with the module key, separated by a colon (:), forms the complete key of a plugin module. Following is a sample atlassian-plugin.xml file without any plugin modules in it: This is a Demo Description 1.0 . . . 1 or more plugin modules . . . The plugin, as you can see from the preceding sample, has details such as description, version, vendor details, and so on. When a plugin is loaded, all the unique modules in it are also loaded. The plugin classes override the system classes, and so if there is an action that has the same alias name as that of a JIRA action, it is the plugin action class that will be loaded. We will see more about extending actions in the coming chapters. Suppose you have a report module in your plugin. It will look as follows: .. The plugin key in the preceding case will be com.jtricks.demo, and the complete module key will be com.jtricks.demo:demo-report. Hang on, before you start writing your little plugin for a much wanted feature, have a look at the Atlassian Marketplace to see if someone else has already done the dirty work for you! Atlassian Marketplace Atlassian Marketplace is a one-stop shop where you can find the entire list of commercial and open source plugins that people around the world have written. See https://marketplace.atlassian.com/plugins/app/jira for more details. Plugin Development Process 10 Troubleshooting A common scenario that people encounter while deploying a plugin is when the plugin fails to load even though everything looks fine. Make sure your plugin's key is unique and is not duplicated in one of your or another third-party's plugin! The same applies to individual plugin modules. Setting up the development environment Now that we know what a plugin is, let's aim at writing one! The first step in writing a JIRA plugin is to set up your environment—if you haven't done that already. In this recipe, we will see how to set up a local environment. To make our plugin development easier, Atlassian provides the Atlassian plugin software development kit (SDK). It comes along with Maven and a preconfigured settings.xml file to make things easier. The Atlassian plugin SDK can be used to develop plugins for other Atlassian products, including Confluence, Crowd, and so on, but we are concentrating only on JIRA. Getting ready Following are the prerequisites for running the Atlassian plugin SDK: ff The default port 2990 for the SDK should be available. This is important because different ports are reserved for different Atlassian products. ff JDK should be installed and Java version 1.6.x is required. The SDK is not yet compatible with Java 1.7. ff Make sure the JAVA_HOME environment variable is set properly and the java -version command outputs the correct Java version details. How to do it… Following are the steps to set up our development environment: 1. Once we have installed Java and the port 2990 is ready, we can download the latest version of Atlassian plugin SDK from https://developer.atlassian.com/ display/DOCS/Getting+Started. Chapter 1 11 2. Install the SDK by following the instructions provided on the Atlassian Developer Documentation page, depending upon the operating system. Let's call this directory SDK_HOME. 3. Add the SDK's bin directory into the environment Path variable. 4. Create a new environment variable, M2_HOME, pointing to the apache-maven directory in your /SDK_HOME/atlassian-plugin-sdk directory. SDK version 4.x and above doesn't need this step. 5. A lot of commonly used dependencies are already available in the repository folder embedded in the SDK. To use this, edit the settings.xml file under M2_HOME/conf/ by modifying the localRepository attribute to point to the embedded repository folder. By default, it will use the USER_HOME/.m2/ repository directory. 6. Install the IDE of your choice. Atlassian recommends Eclipse, IntelliJ IDEA, or NetBeans, as they all support Maven. With the preceding steps executed properly, we have a development environment for JIRA plugins. The next step is to create a skeleton plugin, import it in to your IDE, and start writing some code! Creating the skeleton plugin, deploying it, and so on, is explained in detail in the forthcoming recipes of this chapter. So, ready, set, go… There's more… If you are facing issues while downloading the dependencies using Maven, just read on. Proxy settings for Maven If you are executing behind a firewall, make sure you have configured proxy settings in Maven's settings.xml file. A proxy can be configured as follows: . true http proxy.demo.com 8080 demouser Plugin Development Process 12 demopassword localhost|*.demosite.com . To find out more about proxy settings for Maven and other aspects of it, go to http://maven.apache.org/index.html. Using local Maven If you are a developer, in many cases you will have Maven already installed in your local machine. In that case, point the M2_HOME environment variable to your local Maven, and update the respective settings.xml file with the repository details in the default settings.xml file that ships with the Atlassian plugin SDK. Or, you can simply add the following lines to the existing settings.xml file: atlassian-plugin-sdk file://${env.ATLAS_HOME}/repository true warn false Configuring IDEs to use the SDK If you are using IntelliJ IDEA, configuring it to use the SDK is an easy job because IDEA integrates Maven out of the box. Just load the project by selecting the pom.xml file! See https://developer.atlassian.com/display/DOCS/ Configure+IDEA+to+use+the+SDK for more details. If you are using Eclipse, make sure you have m2eclipse installed. This is because Eclipse integrates Maven through the Sonatype m2eclipse plugin. You can find more details on configuring this at https://developer.atlassian.com/display/DOCS/Set+Up+ the+Eclipse+IDE+for+Linux for Linux, or https://developer.atlassian.com/ display/DOCS/Set+Up+the+Eclipse+IDE+for+Windows for Windows. Chapter 1 13 If you are using Netbeans, check out https://developer.atlassian.com/display/ DOCS/Configure+NetBeans+to+use+the+SDK on how to configure it to use the SDK. Troubleshooting If you see Maven download errors such as "Could not resolve artifact", make sure you verify the following: ff Entry in Maven's settings.xml file is correct. That is, it points to the correct repositories. ff Proxy configuration is done if required. ff Antivirus in the local machine is disabled and/or firewall restrictions removed if none of the above works! Seriously, it makes a difference. See also ff The Creating a skeleton plugin recipe Creating a skeleton plugin In this recipe, we will look at creating a skeleton plugin. We will use the Atlassian plugin SDK to create the skeleton! Getting ready Before beginning this recipe, make sure you have the Atlassian plugin SDK installed. How to do it… Following are the steps to create a skeleton plugin: 1. Open a command window and go to the folder where you want to create the plugin. Make sure you use a directory without any spaces in its name, because there are known issues with the SDK not working in directory names with spaces. See https://studio.atlassian.com/ browse/AMPS-126 for more details. Plugin Development Process 14 2. Type atlas-create-jira-plugin and press Enter. 3. Press 1 if you want to create a JIRA 5 plugin or 2 if you want to create a plugin for JIRA 4 or earlier versions. This step can be avoided if you use the atlas-create-jira5-plugin command instead of atlas-create-jira-plugin. Note the use of jira5 instead of jira! 4. Enter the group-id information when prompted. group-id would normally be coming from your organization name and mostly resembles the Java package. Of course, you can enter a different package name as we move forward if you want to keep it separate. group-id will be used to identify your plugin along with artifact-id, for example, com.jtricks.demo. 5. Enter the artifact-id information (the identifier for this artifact) when prompted. Do not use spaces here, for example, demoplugin. 6. Enter the version information when prompted. The default version is 1.0-SNAPSHOT. Enter a new version if you want to change it or press Enter to keep the default version, for example, 1.0. 7. Enter the package information when prompted. Press Enter if the package value is the same as the group-id value. If not, enter the new value here and press Enter, for example, com.jtricks.mypackage. 8. Confirm the selection when prompted. If you want to change any of the entered values, type N and press Enter. 9. Wait for the BUILD SUCCESSFUL message to appear. Downloading the example code You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com . If you purchased this book elsewhere, you can visit http://www. packtpub.com/support and register to have the files e-mailed directly to you. How it works… A skeleton plugin is nothing but a set of directories and subdirectories, along with a pom.xml file (Maven Project Object Model) and some sample Java and XML files in the appropriate folders. Chapter 1 15 Following is a snapshot of how the project will look like in Eclipse. It also shows the design view of the default atlassian-plugin.xml file: As you can see, there is a pom.xml file at the root level and a src folder. A sample LICENSE file and a README file are also created for you at the root level. Under the src folder, you will find two folders—main and test—with an identical folder structure. All your main Java code goes under the main folder. Any JUnit tests you write will go into the same location under the test folder. There is an additional folder—it—under the test folder, where all the integration tests will go! You will find the plugin descriptor, that is atlassian-plugin.xml, under src/main/ resources with sample values already populated in it. The values shown in the previous screenshot are populated from the pom.xml file. In our case, the plugin key will be populated as com.jtricks.demo:demoplugin when the plugin is built. There are two more folders under the src/test tree. The src/test/resources folder, which will hold any resources required for unit tests or integration tests, and the src/test/ xml folder, which can hold the XML data from any other JIRA instance. If the XML is supplied, the SDK will use it to configure the JIRA instance before running the integration tests. So, that is our plugin skeleton. All that is pending is some useful Java code and some proper module types in the atlassian-plugin.xml file! Plugin Development Process 16 Remember, the first Maven run is going to take some time, as it downloads all the dependencies into your local repository. A coffee break might not be enough! If you have a choice, plan your meals. There's more… Sometimes, for the geeks, it is much easier to run a single command to create a project, without bothering about the step-by-step creation. In the next section, we will quickly see how to do it. We will also have a look at how to create an Eclipse project if you opt out of installing m2eclipse. One step to your skeleton plugin You can ignore the interactive mode by passing the parameters such as group-id, artifact-id, and so on, as arguments to the atlas-create-jira-plugin command: atlas-create-jira5-plugin -g my_groupID -a my_artefactId -v my_version -p my_package ---non-interactive For the example values we saw earlier, the single line command will be as follows: atlas-create-jira5-plugin -g com.jtricks.demo -a demoplugin -v 1.0 -p com.jtricks.mypackage --non-interactive You can pick and choose the parameters and provide the rest in an interactive mode too! Creating an Eclipse project If you are not using m2eclipse, just run the following command from the folder that contains the pom.xml file: atlas-mvneclipse:eclipse This will generate the plugin project for Eclipse and you can then import this project into the IDE. Execute atlas-mvn eclipse:clean eclipse:eclipse if you want to clean the old project and create again! With IDEA or m2eclipse, just opening a file will do. That is, you can just import the project using the File | Import | Existing Maven Projects option, and select the relevant project. See also ff The Adding plugin modules recipe ff The Deploying a JIRA plugin recipe ff The Making changes to and redeploying a plugin recipe Chapter 1 17 Adding plugin modules In this recipe, we will look at adding plugin modules into an existing plugin project. Getting ready Make sure the plugin project already exists, or create a new skeleton project as explained in the previous recipe. How to do it… Following are the steps to add plugin modules into an existing plugin project: 1. Open a command window and go to the plugin project folder where pom.xml resides. 2. Type atlas-create-jira-plugin-module and press Enter. This will show all the available plugin modules as a numbered list, as shown in the following screenshot: Plugin Development Process 18 3. Select the number against the module that you are planning to add. For example, type 25 and press Enter if you want to add a simple Web Item module to the plugin. 4. Follow the instructions to provide details required for the selected module. Some of the options may have default values. Some modules might also have an advanced setup. Type Y and press Enter when prompted if you want to go to Advanced Setup. If not, type N and press Enter. 5. Once the module is completed, type Y or N and press Enter when prompted, depending on whether you want to add another module or not. 6. Repeat the steps for every module you want to add. 7. Wait for the BUILD SUCCESSFUL message to appear when no more modules are there to be added. How it works… Similar to the skeleton plugin creation, a set of directories and subdirectories are created during the process of adding plugin modules, along with a number of Java files or Velocity templates required for the selected plugin module. It also adds the plugin module definition in the atlassian-plugin.xml file based on our inputs in step 4. A sample plugin descriptor, after adding the Web Item module, is shown as follows: ${project.description} ${project.version} The My Web Item Plugin http://www.j-tricks.com Chapter 1 19 As you can see, a web-item module is added. You can also see a resource module, which is added automatically the first time a plugin module is created. This will be the i18n resource file for the plugin, and more key-value pairs will be added into this file when more modules are added. The file has the name {plugin-artifact-name}.properties and is created under the src/main/resources{plugin-group-folder} folder. In our example, a demo. properties file is created under the src/main/resources/com/jtricks/demo folder. A sample property file is shown as follows: my-web-item.label=J-Tricks my-web-item.name=My Web Item my-web-item.description=The My Web Item Plugin See also ff The Creating a skeleton plugin recipe ff The Deploying a JIRA plugin recipe ff The Making changes to and redeploying a plugin recipe Deploying a JIRA plugin In this recipe, we will see how to deploy a plugin into JIRA. We will see both the automated deployment using the Atlassian plugin SDK and the manual deployment. Getting ready Make sure you have the development environment set up as we discussed earlier. Also, the skeleton plugin should now have the plugin logic implemented in it. How to do it… Installing a JIRA plugin using the Atlassian plugin SDK is a cakewalk. The following steps show how it is done: 1. Open a command window and go to your plugin's root folder, that is, the folder where your pom.xml file resides. Plugin Development Process 20 2. Type atlas-run and press Enter. It is possible to pass more options as arguments to this command. The details regarding this can be found at https://developer. atlassian.com/display/DOCS/atlas-run. You will see a lot of things happening as Maven downloads all the dependent libraries into your local repository. As usual, it is going to take a lot of time when you run it for the first time. 3. If you are on Windows, and if you see a security alert popping up, click on Unblock to allow incoming network connections. 4. When the installation is complete, you will see the following message: [WARNING] [talledLocalContainer] INFO: Server startup in 123558 ms [INFO] [talledLocalContainer] Tomcat 6.x started on port [2990] [INFO] jira started successfully and available at http:// localhost:2990/jira [INFO] Type CTRL-C to exit 5. Open http://localhost:2990/jira in your browser. 6. Log in using the username as admin and password as admin. 7. Test your plugin! You can always go to Administration | Plugins | Manage Add-ons to confirm that the plugin is deployed properly. 8. If you already have a local JIRA installed, or if you want to manually install your plugin, all you need to do is to package the plugin JAR and install it via the Universal Plugin Manager (UPM), as described in detail at https://confluence. atlassian.com/display/UPM/Installing+Add-ons#InstallingAdd-ons- Installingbyfileupload. Or, you can copy it across to the JIRA_Home/plugins/ installed-plugins directory and restart JIRA. You can package the plugin using the following command: atlas-mvn clean package Use atlas-mvn clean install if you also want to install the packaged plugin into your local repository. How it works… There is only one single command that does the whole thing: atlas-run. When you execute this command, it does the following tasks: ff Builds your plugin JAR file ff Downloads the latest/specified version of JIRA to your local machine if it is the first time you are running the command Chapter 1 21 ff Creates a virtual JIRA installation under your plugin's /target folder ff Copies the JAR file into the /target/jira/home/plugins/installed-plugins directory ff Starts JIRA in the Tomcat container Now, if you look at your target folder, you will see a lot of new folders that were created for the virtual JIRA installation! The two main folders are the container folder, which has the Tomcat container setup, and the jira folder, which has the JIRA WAR along with the JIRA home setup! You will find the database (HSQLDB), indexes, backups, and attachments under /target/ jira/home. You can also see your jira-webapp at /target/container/tomcat6x/ cargo-jira-home/webapps/jira. If you have any JSPs that need to be put under the webapp, you will have to copy it to the appropriate folder under the aforementioned path! There's more… It is also possible to use a specific version of JIRA or to re-use the data that we have used for testing. To find out how, just read on. Using a specific version of JIRA As mentioned earlier, atlas-run deploys the latest version of JIRA. But what if you want to deploy the plugin into an earlier version of JIRA and test it? There are two ways to do this: ff Mention the JIRA version as an argument to atlas-run. Make sure you run atlas-clean if you already have the latest version deployed. 1. Run atlas-clean (if required). 2. Run atlas-run -v 5.0 or atlas-run -version 5.0 if you are developing for JIRA version 5.0. Replace the version number with a version of your choice. ff Permanently change the JIRA version in your plugin's pom.xml file. 1. Go to your pom.xml file. 2. Modify the jira.version property value to the desired version. 3. Modify the jira.data.version property value to a matching version. Following is how it will look for JIRA 5.0: 5.0 5.0 Plugin Development Process 22 Re-using the configurations in each run Suppose you have added some data onto virtual JIRA; how do you retain it when you clean startup JIRA next time? This is where a new SDK command comes to our rescue. After the atlas-run command has finished its execution, that is, after you have pressed Ctrl + C, execute the following command: atlas-create-home-zip This will generate a file named generated-test-resources.zip under the target folder. Copy this file to the /src/test/resources folder or any other known locations. Now modify the pom.xml file to add the following entry under configurations in the maven-jira- plugin plugin section: ${basedir}/src/test/resources/generated-test- resources.zip Modify the path accordingly. This will re-use the configurations the next time you run atlas-run. Troubleshooting Following are some points to remember: ff Missing a JAR file exception? Make sure the local-repository attribute in the settings.xml file points to the embedded Maven repository that comes with the SDK. If the problem still persists, manually download the missing JAR files and use atlas-mvn install to install them into the local repository. Watch out for the proxy settings or antivirus settings that can potentially block the download in some cases! ff Getting a BeanCreationException error? Make sure your plugin is of version 2. Check your atlassian-plugin.xml file to see if the plugins-version="2" entry is there or not. If not, add the entry shown as follows: Run atlas-clean followed by atlas-run after you have added the preceding entry. Chapter 1 23 Making changes to and redeploying a plugin Now that we have deployed the test plugin, it is time to add some proper logic, redeploy the plugin, and test it. Making the changes and redeploying a plugin is pretty easy. In this recipe, we will quickly look at how to do this. How to do it… You can make changes to the plugin and redeploy it while the JIRA application is still running. Following is how we do it: 1. Keep the JIRA application running in the window where we executed atlas-run. 2. Open a new command window and go to the root plugin folder where your pom.xml file resides. 3. Run atlas-cli. 4. Wait for the Waiting for commands message to appear. 5. Run the pi. pi stands for plugin install and this will compile your changes, package the plugin JAR, and install it into the installed-plugins folder. As of JIRA 4.4, all the modules are reloadable and hence can be redeployed using this technique. There's more… It is also possible to run the plugin in debug mode and point your IDE's remote debugger to it. Debugging in Eclipse Following are the steps to debug your plugin in Eclipse: 1. Use atlas-debug instead of atlas-run. 2. Once the virtual JIRA is up and running with your plugin deployed in it, go to Run | Debug Configurations in Eclipse. 3. Create a new remote Java application. 4. Give your application a name, keep the defaults, and give the port number as 5005. This is the default debug port on which the virtual JIRA runs. That's it! Happy debugging! Plugin Development Process 24 See also ff The Setting up the development environment recipe ff The Creating a skeleton plugin recipe ff The Using FastDev for plugin development recipe Using FastDev for plugin development We have seen how to use atlas-cli to reload a plugin without having to restart atlas-run. It is a pretty good way to save time, but Atlassian have walked the extra mile to develop a plugin named FastDev, which can be used to reload plugin changes during development to all Atlassian applications including JIRA. And that, from the browser itself! Getting ready Create a plugin and use atlas-run to run the plugin as discussed in the aforementioned recipe. Let us assume we are doing it on the plugin we created in the previous recipe—the one with the sample Web Item. If we run this sample Web Item plugin, using atlas-run, we can access JIRA at port 2990 as mentioned earlier, and the Web Item will look as highlighted in the following screenshot: As you can see, the J-Tricks link appears along with the Profile link under the personal section. But what if you wanted the link at the jira-help section, along with Online Help, About JIRA, and so on? Chapter 1 25 How to do it… Following are the simple steps to make the aforementioned change to the plugin and reload it using FastDev: 1. Access the FastDev servlet on the browser using the following path: http://localhost:2990/jira/plugins/servlet/fastdev You will find the servlet as shown in the following screenshot: 2. Make the change to your plugin. In this example, the change is pretty small. All we do is modify the atlassian-plugin.xml file to change the section attribute in the web-item module from section="system.user.options/personal" to section="system.user.options/jira-help". 3. Click on the Scan and Reload button on the servlet (shown in the previous screenshot). On clicking the button, FastDev will reload the plugin. You can track the progress and see the logs on the browser itself, as shown in the following screenshot: Plugin Development Process 26 4. Once the plugin is successfully reloaded, you will see the following screenshot with a success message: 5. Reload the JIRA page to see if the change is effective. In our example, the new screen will be as follows: As you can see, the J-Tricks link has moved to the help section. Using FastDev is very effective while building a plugin from scratch and testing it, as the pieces fall into the right places gradually. How it works... When the Scan and Reload button is clicked, FastDev looks for files that have changed since the last time a plugin was installed. If it detects any changes, it starts a Maven process that reinstalls the plugin. Chapter 1 27 More information on FastDev can be found at https://developer. atlassian.com/display/DOCS/Automatic+Plugin+Reinst allation+with+FastDev. There's more… There is more to FastDev than the default configurations. They can be set in your plugin's pom.xml file by adding the required property to the systemPropertyVariables node. You will find the details in the Atlassian documentation (see the previous note box), but the most useful ones are mentioned later. Adding ignored files While looking for changes, FastDev ignores certain files such as .js or .css files that don't need a reinstall of the plugin. The following table shows the full list of files/directories that are ignored: Type Property name Default(s) Directory fastdev.no.reload.directories .svn Extension fastdev.no.reload.extensions .js, .vm, .vmd, .fm, .ftl, .html, .png, .jpeg, .gif, .css File fastdev.no.reload.files  – If you want to ignore additional files or directories, you can add them using the preceding properties, shown as follows: ... images classpath ${basedir}/src/main/resources/LICENSE.txt Plugin Development Process 28 Changing admin credentials FastDev uses the default admin/admin credential to reinstall the plugin. But if the username or password is different, use the fastdev.install.username and fastdev.install. password properties, shown as follows: ... myusername mypassword See also ff The Setting up the development environment recipe ff The Creating a skeleton plugin recipe ff The Making changes to and redeploying a plugin recipe Testing and debugging In the world of test-driven development (TDD), writing tests is a part and parcel of the development process. I don't want to bore you with why testing is important! Let us just assume that this holds true for a JIRA plugin development as well. In this recipe, we will see the various commands for running unit tests and integration tests in JIRA plugins. If you are wondering what exactly TDD is, just go to http://en.wikipedia.org/wiki/Test-driven_development. Getting ready Make sure you have the plugin development environment set up and the skeleton plugin created! You might have noticed that there are two sample test files—one each for unit tests and integration tests—created under the src/test/java/your_package/ and src/ test/ java/it folders. Once you have done this, it is time to write some tests and run those tests to make sure things work as expected! Chapter 1 29 How to do it... Without further delay, lets perform the following steps: 1. The first step is to write some tests! We recommend you use some powerful testing frameworks such as JUnit, in collaboration with mocking frameworks such as PowerMock or Mockito. Make sure you have the valid dependencies added onto your pom.xml file. 2. Let us now make a huge assumption that you have written a few tests! Following is the command to run your unit tests from the command line: atlas-unit-test The normal Maven command, atlas-mvn clean test, also does the same thing. 3. If you are running the integration tests, the command to use is: atlas-integration-test Or, the Maven command to use is: atlas-mvn clean integration-test 4. Once we are onto the stage of running tests, we will see them failing at times. Here comes the need for debugging. Checkout the *.txt and *.xml files created under target/ surefire-reports/, which have all the required information on the various tests that are executed. 5. Now, if you want to skip the tests at the various stages, use -skip-tests. For example, atlas-unit-test --skip-tests will skip the unit tests. 6. You can also use the Maven options directly to skip the unit/integrations tests or both together: ‰‰ -Dmaven.test.skip=true: Skips both unit and integration tests ‰‰ -Dmaven.test.unit.skip=true: Skips unit tests ‰‰ -Dmaven.test.it.skip=true: Skips integration tests How it works… The atlas-unit-test command merely runs the related Maven command, atlas-mvn clean test, in the backend to execute the various unit tests. It also generates the outputs into the surefire-reports directory for reference or debugging. The atlas-integration-test command does a bit more. It runs the integration tests in a virtual JIRA environment. It will start up a new JIRA instance running inside a Tomcat container, set up the instance with some default data including a temporary license that lasts for three hours, and execute your tests! Plugin Development Process 30 But how does JIRA differentiate between the unit tests and integration tests? This is where the folder structure plays an important role. Anything under the src/test/java/it/ folder will be treated as integration tests and everything else will be treated as unit tests! There's more… It is also possible to use custom data for integration tests and to test it against different JIRA versions. Using custom data for integration/functional tests While atlas-integration-test makes our life easier by setting up a JIRA instance with some default data in it, we might need some custom data as well to successfully run a few functional tests. We can do this in a few simple steps: 1. Export the data from a preconfigured JIRA instance into XML. 2. Save it under the src/test/xml/ directory. 3. Provide this path as the value for the jira.xml.data.location property in the localtest.properties file under src/main/resources. The XML resource will then be imported to JIRA before the tests are executed. Testing against different versions of JIRA/Tomcat Just like the atlas-run command, you can use the -v option to test your plugin against a different version of JIRA. Just like before, make sure you run atlas-clean before executing the tests if you had tested it against another version earlier. You can also use the -c option to test it against a different version of the Tomcat container. For example, atlas-clean && atlas-integration-test -v 3.0.1 -c tomcat5x will test your plugin against JIRA version 3.0.1 using Tomcat container 5. See also ff The Setting up the development environment recipe ff The Deploying a JIRA plugin recipe 2 Understanding the Plugin Framework In this chapter, we will cover more details on the JIRA architecture and the plugin framework. We will also cover the following recipes: ff Modifying Atlassian bundled plugins ff Converting plugins from v1 to v2 ff Adding resources into plugins ff Adding web resources into plugins ff Building JIRA from source ff Adding new webwork actions to JIRA ff Extending a webwork action in JIRA ff Capturing plugin installation/uninstallation events Introduction As we saw in the previous chapter, the JIRA plugin development process is probably an easier task than we expected it to be. With the help of Atlassian Plugin SDK, developers can spend more time worrying about the plugin logic than on the troublesome deployment activities. And yes, after all, it is the plugin logic that is going to make an impact! This chapter details how the various components fit into JIRA's architecture and how JIRA exposes the various pluggable points. We will also look at an overview of JIRA's system plugins to find out how JIRA uses the plugin architecture to its own benefit, followed by some useful recipes. Understanding the Plugin Framework 32 JIRA architecture We will quickly see how the various components within JIRA fit in to form the JIRA we know. It is best described in a diagram, and Atlassian has a neat one along with a detailed explanation at https://developer.atlassian.com/display/JIRADEV/ JIRA+Architectural+Overview. We will redraw the diagram a little bit to explain it in a brief but useful way. Third-party components Before we dig deeper into the JIRA architecture, it is probably helpful to understand a few key components and familiarize yourself with them. JIRA's major third-party dependencies are outlined next. It is not mandatory to know all about these frameworks, but it will be very helpful during plugin development if you have an understanding of these. Webwork Webwork is nothing but a Java web application development framework. The following is a quick overview of webwork as described in the OpenSymphony documentation: "It is built specifically with developer productivity and code simplicity in mind, providing robust support for building reusable UI templates, such as form controls, UI themes, internationalization, dynamic form parameter mapping to JavaBeans, robust client- and server-side validation, and much more." Note that JIRA uses webwork1 and not 2. In this book, all instances of webwork refer to the webwork1 version. JIRA itself refers to the technology as webwork, but you will notice that the files, plugin modules, and so on, use webwork1 just to emphasize the version. Seraph Seraph is Atlassian's open source web authentication framework. It provides a simple, extensible authentication system that JIRA uses for all authentication purposes. Read more about Seraph at http://docs.atlassian.com/atlassian-seraph/ latest/. OSUser OSUser is OpenSymphony's user and group management framework. It is designed to provide a simple-to-use API for user management. JIRA uses OSUser framework in versions prior to JIRA 4.3. Chapter 2 33 Embedded Crowd JIRA 4.3+ uses Crowd, which is Atlassian's Identity management and Single Sign-on tool, for user management. JIRA embeds a subset of Crowd's core modules, which provides the user with management capabilities. More information on Crowd can be found at http://www.atlassian.com/software/ crowd/overview. PropertySet PropertySet is again another open source framework from OpenSymphony that helps you to store a set of properties against any ''entity" with a unique ID. The properties will be key/value pairs and can only be associated with a single entity at a time. Active Objects Active Objects is a new Object Relational Mapping (ORM) layer into Atlassian products. It is implemented as a plugin and provides data storage that can be used by plugins to persist their private data. It enables easier, faster, and scalable data access compared to PropertySet. More details on Active Objects can be found at https://developer.atlassian.com/ display/AO/Active+Objects. We have also discussed about Active Objects in Chapter 10, Dealing with JIRA Database. OSWorkflow OSWorkflow is yet another open source framework from the OpenSymphony group. It is an extremely flexible workflow implementation that is capable of driving complex conditions, validators, post functions, and so on, along with many other features. OFBiz Entity Engine OFBiz stands for "Open For Business", and the OFBiz Entity Engine is a set of tools and patterns used to model and manage entity-specific data. As per the definition from the standard Entity-Relation modeling concepts of Relational Database Management Systems: An Entity is a piece of data defined by a set of fields and a set of relations to other entities. Read more about Entity Modeling and Entity-Relation concepts at http://ofbiz.apache.org/docs/entity.html. Understanding the Plugin Framework 34 Apache Lucene The following is a simple definition of Apache Lucene that you can find in its documentation: Apache Lucene(TM) is a high-performance, full-featured text search engine library written entirely in Java. It is a technology suitable for nearly any application that requires full-text search, especially cross-platform. More about Lucene and its potential can be found at http://lucene.apache.org/core/ index.html. Atlassian Gadget JavaScript Framework JIRA 4 introduces a powerful gadget framework. Atlassian has gone OpenSocial with gadgets and in order to help developers in creating gadgets, Atlassian has introduced a Gadget JavaScript Framework that encapsulates a lot of common requirements and functionalities used between gadgets. More about gadget development can be read at https://developer.atlassian.com/ display/GADGETS/Using+the+Atlassian+Gadgets+JavaScript+Framework. Quartz Quartz is an open source job scheduling service. It can be used to create jobs that can be scheduled within any Java EE and SE applications. The tasks are defined as standard Java components and scheduler includes many enterprise-class features, such as JTA transactions and clustering. Read more at http://www.quartz-scheduler.org/. Architecture explained It is best to learn the intricacies of system architecture with the help of a diagram. For the benefit of a brief but meaningful explanation on the JIRA architecture, let us have a quick look (or a long stare, whichever you are more comfortable with!) at the following diagram: Chapter 2 35 User/Browser Plugins RDBMS Scheduler Apache Lucene RPC Jelly QuartzOFBiz Entity Engine OSWorkflow Active Objects Embedded Crowd PropertySet Lucene indexes JIRA core utility and Manager classes Webworks Seraph JSP/Velocity JIRA is a web application built using the MVC architecture. It is fully written in Java and is deployed as a WAR file into a Java Servlet Container such as Tomcat. The majority of the JIRA core functionality revolves around the JIRA Utility and Manager Classes, which thus becomes the heart of JIRA. It also interacts with a lot of third-party components, which we saw earlier, to deliver powerful functionalities such as workflows, permissions, user management, and searching. As with any other web application, let us start with the incoming requests. Users interact with JIRA using web browsers, but there are other ways to interact with JIRA, such as using the Jelly scripts or by making remote calls using REST/SOAP/XML-RPC. Understanding the Plugin Framework 36 Authentication and user management The user authentication, whichever way the request comes, is done in JIRA using Seraph, Atlassian's open source web authentication framework. Seraph is implemented as a servlet filter, and it intercepts each and every incoming request and associates them with a specific user. It supports various authentication mechanisms such as HTTP Basic Authentication, form-based authentication, and it even looks up already stored credentials in a user session when implemented with SSO (Single Sign On). However, Seraph doesn't do any user management itself. It delegates this to the Embedded Crowd framework (OSUser in JIRA versions prior to 4.3). One additional thing that Seraph does in JIRA is to intercept URLs starting with /admin/ and allow users access only if they have the "Global Admin" permission. Coming back to authentication and other user management functions, it is OSUser that does the work for JIRA in versions prior to 4.3. It does the following activities: ff User management: Creates/updates/deletes users and stores the details in the JIRA database. It also stores user preferences. ff Group management: Creates/updates/deletes groups and stores the details in the JIRA database. It also manages group memberships. ff Authentication: Password matching. From JIRA 4.3 onward, user management in JIRA is done using Crowd. Crowd is a single sign- on and identity management system from Atlassian, which is now embedded in JIRA 4.3+. Plugin developers can now use CrowdService to manage users and groups, for which more information can be found at http://docs.atlassian.com/crowd/current/com/ atlassian/crowd/embedded/api/CrowdService.html. The following are the core Crowd functionalities: ff User, Group, and Membership management—stores all details in JIRA DB ff Authentication—password matching ff Allows connection to external user directories such as LDAP, AD, and Crowd. ff Synchronizes external user data to local database for faster access Chapter 2 37 Property management JIRA lets you add key/value pairs as properties on any available entity such as User, Group, Project, and Issue. It uses OpenSymphony's PropertySet to do this. Three major cases where PropertySet is used internally in JIRA are as follows: ff To store user preferences by the OSUser framework like e-mail, full name, and so on ff To store application properties ff To store chosen preferences of Portlets/Gadgets on user dashboards We can also use the PropertySet in our plugins to store custom data as key/value pairs. In earlier versions of JIRA, PropertySet was the only technology used to store plugin information and other data related to plugins. But JIRA now supports a new technology called Active Objects, which can be used to store plugin data. This is explained in detail in Chapter 10, Dealing with the JIRA Database. Presentation The presentation layer in JIRA is built using JSPs and Velocity templates. Another experimental component available in JIRA 5 for the presentation layer is soy templates. The web requests coming on to JIRA are processed by OpenSymphony's webwork1 framework. The requests are handled by webwork actions that internally use the JIRA Service layer. The service classes expose the core utility and Manager classes that perform the tasks behind the scenes! Database JIRA talks to its database using the OFBiz Entity Engine module. Its database schema is defined in the entitmodel.xml and entitygroup.xml files residing in the WEB-INF/ classes/entitydefs folder. The entity configuration details goes in to entityengine. xml under WEB-INF/classes, whereas the DB connectivity details are stored in dbconfig.xml residing in the JIRA Home directory. JIRA supports a wide variety of database products, for which more details can be found at https://confluence.atlassian.com/display/JIRA/ Connecting+JIRA+to+a+Database. Understanding the Plugin Framework 38 Workflows Workflows are one of the most important features in JIRA. They provide us with a highly configurable workflow engine that uses OpenSymphony's OSWorkflow behind the scenes. It lets us customize the workflows by adding new steps and transitions, and for each transition we can add conditions, validators, or post functions. We can even write plugins to add more of these, in addition to the ones that ship with JIRA. We will see all that in detail in the coming chapters. Searching JIRA uses Apache Lucene to perform indexing in JIRA. Whenever an issue is changed in JIRA, it performs a partial re-indexing to update the related indexes. JIRA also lets us do a full re-index at any time manually from the Administration screen. Searching in JIRA is done using these indexes, which are stored in the local drive. We can even store search queries as filters whose results get updated as the indexes changes. Scheduled jobs JIRA uses the Quartz API to schedule jobs. These jobs, including the subscriptions to the filters and the custom ones we add, are stored in the JIRA database, and are executed by the Quartz job scheduling service. JIRA's built-in scheduled job details can be found at scheduler-config.xml. It is possible to schedule new events in JIRA using the SAL services implementation. As Atlassian puts it: The Shared Access Layer, or SAL inshort, provides a consistent, cohesive API to common plugin tasks, regardless of the Atlassian application into which your plugin is deployed. More information on scheduling events in JIRA using SAL can be found at https://developer.atlassian.com/display/DOCS/Plugin+Tutorial+- +Scheduling+Events+via+SAL. Plugins Last but not least, plugins fit into the JIRA architecture to provide extra functionalities or to alter some of the existing ones. The plugins mostly use the same JIRA core utility classes and Manager classes as webwork actions do, but in some cases also add to the list. There are different pluggable points in JIRA, which we will see in detail in this chapter. Chapter 2 39 This, I hope, gives you a brief introduction to the JIRA architecture and the major components used in it. We will see most of these in detail in the coming chapters, and we will cover how to customize them by writing plugins. Off you go! Types of plugin modules Let us briefly see the different types of plugin modules supported in JIRA5. All these modules are various extension points, using which we can not only add new functionalities in to JIRA, but also extend some of the existing functionalities. Let us group them based on functionality instead of seeing them all together! Reporting Module type Description Gadget Adds new Gadgets into the user's dashboard. These gadgets can also be accessed from other applications. Report Adds new reports in to JIRA. Workflows Module type Description workflow-condition Adds new workflow conditions to the JIRA workflow. It can then be used to limit the workflow actions to users, based on pre-defined conditions. workflow-validator Adds new workflow validations to the JIRA workflow. Validations can be used to prevent certain workflow actions when certain criteria are not met. workflow-function Adds new workflow post functions to the JIRA workflow. These can be used to perform custom actions after a workflow action is executed. Custom fields Module type Description customfield-type Adds new custom field types to JIRA. We can customize the look and feel of the fields in addition to creating custom logic. See also customfield-searcher. Understanding the Plugin Framework 40 Searching Module type Description customfield-searcher Adds new field searchers in to JIRA. The searcher needs to be mapped with the relevant custom fields. jql-function Adds new JQL Functions to be used with JIRA's advanced searching. search-request-view Adds a new view in the Issue Navigator. They can be used to show the search results in different ways. Links and tabs Module Type Description web-section Adds new sections in application menus. Each section can contain one or more links under it. web-item Adds new links that will appear at a defined section. The section here can be the new ones we added earlier or the existing JIRA web sections. project-tabpanel Adds new tabs to the Browse Project screen. We can define what has to appear in the tab. component-tabpanel Adds new tabs to the Browse Component screen. As above, we can define what appears in the tab. version-tabpanel Adds new tabs to the Browse Version screen. Same as above. issue-tabpanel Adds new tabs to the View Issue screen. Similar to other tabs, here also we can define what appears in the tab. web-panel Defines panels or sections that can be inserted into an HTML page. issue-link-renderer Allows you to add new custom renderers for issue links to JIRA. This is useful for Remote Issue Links. Chapter 2 41 Remote invocation Module type Description rest Creates new REST APIs for JIRA to expose more services and data entities. rpc-soap Publishes new SOAP end-points for JIRA. It is deployed as a new SOAP service and exposes a new WSDL with the operations we have published in the plugin. rpc-xmlrpc Same as above. Exposes XML-RPC endpoints, instead of SOAP, within JIRA. Actions and components Module type Description webwork Adds new webwork actions along with views into JIRA, which can add new functionality or override existing ones. component Adds components to JIRA's component system. These are then available for use in other plugins and can be injected into them. component-import Imports components shared by other plugins. Other plugin modules Module type Description resource Adds downloadable resources into the plugins. A resource is a non-Java file such as JavaScript, CSS, image files, and so on. web-resource Similar to the above, this adds downloadable resources into the plugins, but these are added to the top of the page with the cache-related headers set to never expire. We can also specify the resources to be used only in specific contexts. Multiple resource modules will appear under a web-resource module. web-resource- transfomer Manipulates static web resources before they are batched and delivered to the browser. servlet Deploys a JAVA servlet onto JIRA. servlet-context- listener Deploys a JAVA Servlet Context Listener. servlet-context- param Sets parameters in the Servlet context shared by the plugin's servlets, filters, and listeners. servlet-filter Deploys a JAVA servlet filter into JIRA. The order and position in the application's filter chain can be specified. Understanding the Plugin Framework 42 Module type Description user-format Adds custom behaviors for user details. This is used to enhance the user profile. keyboard-shortcut Available only from 4.1.x. This defines new keyboard shortcuts for JIRA. You can also override the existing shortcuts from JIRA 4.2.x! ao Allows the use of the Active Objects service to persist plugin data. module-type Dynamically adds new plugin module types to the plugin framework. The new module can be used by other plugins. What goes into atlassian-plugin.xml? Let's look deeper into the plugin descriptor named atlassian-plugin.xml. Following is how the plugin descriptor will look when the skeleton plugin is created: ${project.description} ${project.version} We need to add more details into it depending on the type of plugin we are going to develop. The plugin descriptor can be divided into three parts: ff atlassian-plugin element: This forms the root of the descriptor. The following attributes populate the atlassian-plugin element: ‰‰ key: This is probably the most important part. It should be a unique key across the JIRA instance and will be used to refer the different modules in the plugin, just like we use the packages in a Java application. If you see ${project.groupId}.${project.artifactId} as the plugin key, it picks up the values from your pom.xml file. When the plugin is built, the key will be YOUR_GROUP_ID.YOUR_ARTIFACT_ID. ‰‰ name: Give an appropriate name for your plugin. This will appear in the plugin menu under administration. Chapter 2 43 ‰‰ plugins-version: This is different from the version attribute. plugins-version defines whether the plugin is version 1 or 2. plugins- version="2" defines the plugin as a version 2 plugin. Remove the entire attribute to make it a version 1 plugin. ‰‰ state: This is an optional element to define the plugin as disabled, by default. Add state="disabled" under the atlassian-plugin element. ff plugin-info element: This section contains information about a plugin. It not only provides information that is displayed to administrators but also, optionally, provides bundle instructions to the OSGi network: ‰‰ description: A simple description about your plugin. ‰‰ version: The actual version of your plugin that will be displayed under the plugin menu along with the name and description. This version will be checked against the version in Marketplace to show available updates! ‰‰ application-version: Here you can define the minimum and maximum version of the JIRA application that is supported by your plugin. will be supported from 5.0.2 to 5.1. Remember, this is only for information's sake. The plugin might still work fine in JIRA 5.2! ‰‰ vendor: Here you can provide details about the plugin vendor. It supports two attributes—name and url—which can be populated with the organization's Name and URL respectively. Similar to the plugin key, you can populate this from the pom.xml file, as you would have noticed in the skeleton descriptor. ‰‰ param: This element can be used to define name/value attributes for the plugin. You can pass as many attributes as you want. For example, /secure/JTricksConfigAction. jspa defines the configuration URL for our demo plugin. ‰‰ bundle-instructions: Here we define the OSGi bundle instructions that will be used by the Maven Bundle Plugin while generating the OSGi bundle. More about this can be read under aQute's bnd tool at http://www.aqute.biz/Code/Bnd. Understanding the Plugin Framework 44 Following are the two elements in a snapshot: ‰‰ Export-Package: This element defines the package in this plugin that can be exposed to other plugins. All other packages will remain private. ‰‰ Import-Package: This element defines the packages that are outside this plugin but that are exported in other plugins. ff Plugin modules: This is the section where the actual plugin modules (which we saw a bit earlier and will see in detail later in this book) will appear. Hopefully, you now have your plugin descriptor ready with all the necessary attributes! Working with the Plugins1 and Plugins2 versions Let us also quickly see how to deal with the Plugins1 and Plugins2 versions. Before we go on to the details, it is essential to understand the importance of both these versions. Pre 4.x, JIRA used to support only the Plugins1 version. So why do we need the Plugins2 version? The key motive behind version2 plugins is to keep the plugins as a bundle isolated from the other plugins and the JIRA core classes. It makes use of the OSGi platform (http://www.osgi.org/) to achieve this. While it keeps the plugins isolated, it also gives you a way to define dependencies between plugins, leaving it to the plugin developer's convenience. It even lets you import or export selected packages within the plugin giving increased flexibility. The fact that the version2 plugins are deployed as OSGi bundles also means that the plugins are dynamic in nature. They may be installed, started, updated, stopped, and uninstalled at any time during the running of the framework. It is the developer's choice to go for the Plugins1 version or the Plugins2 version, depending on the nature of the plugin. Chapter 2 45 The following table shows the key differences at the development stage of plugin development for both the versions: Plugins1 Plugins2 Version No plugins-version element in atlassian- plugin.xml. Include the plugins-version element in the atlassian-plugin.xml file as follows: External Dependencies Include the dependent libraries with the provided scope in your pom.xml file if the JARs are added into WEB-INF/lib, or compile the scope if the JARs should be embedded into the plugin. Dependent libraries must be included in the plugin as the plugin cannot make use of resources under WEB-INF/lib. This can be done in two ways: ff Provide the scope in the pom.xml file as compile. In this case, the JARs will be picked up by the plugin SDK and added into the META-INF/lib folder of the plugin. ff Manually add the dependent JAR files into the META-INF/lib directory inside the plugin. You can also make your plugin dependent on other bundles. See the Managing complex dependencies row in this table. Dependency injection Done by Picocontainer in JIRA. All registered components can be injected directly. Done by the plugin framework. Not all JIRA's core components are available for injection in the constructor. Use the component-import module to access some of the dependencies that are not directly accessible within the plugin framework. Use it also to import public components declared in other plugins. Declaring new components Use the component module to register new components. Once done, it is available to all the plugins. Use the component module to register components. To make it available to other plugins, set the public attribute to true. It is false by default, making it available only to the plugin in which it is declared. Understanding the Plugin Framework 46 Plugins1 Plugins2 Managing complex dependencies All the classes in version1 plugins are available to all other v1 plugins and JIRA core classes. Version2 plugins allow us to optionally import/export selected packages using bundle-instructions in the plugin descriptor, or alternatively, by the Import- Package/Export-Package options while building the bundle. The bundle dependency system therefore allows you to define complex dependencies between plugins, eliminating the classpath contradictions and the upgradation of plugins. The following table shows the key differences at the installation stage of plugin development for both the versions: Plugins1 Plugins2 Plugins must be on the application classpath. Hence deploy it under the WEB-INF/ lib folder. Plugins must not be on the application classpath. It is loaded using the plugin framework. Hence the plugin is deployed under ${jira-home}/plugins/installed- plugins/. jira-home is declared in the jira-application. properties file under WEB-INF/classes. Right, we now know how the two plugin versions work. Maybe, it is time to see the plugins that JIRA comes with! JIRA system plugins In this section, we will see a brief overview of the JIRA system plugins. A lot of JIRA's functionality is written in the form of plugins. This not only showcases what we can achieve using plugins, but also helps us, as developers, to understand how the various pieces fit together. If it is the atlassian-plugin.xml file that describes the plugin functionalities, JIRA maintains the information in *.xml files placed under WEB-INF/classes. You will also find the related classes in the exploded folders under WEB-INF/classes. Chapter 2 47 Let us have a quick look at the various system plugin XMLs that can be found in WEB-INF/classes and the functionality they support: System plugin XML Functionality system- contentlinkresolvers- plugin.xml System content link resolvers: Resolves parsed content links into link objects. ff Attachment link resolver ff Anchor link resolver ff JIRA issue link resolver ff User profile link resolver system-customfieldtypes- plugin.xml JIRA system custom fields: All the out-of-the-box custom fields in JIRA and the searcher associations. Examples are: ff Text field ff Text area ff User picker ff Select system-footer-plugin.xml This plugin renders the content of the footer in JIRA. system-issueoperations- plugin.xml System issue operations: Renders the issue operations using web-items grouped using web-sections. Examples are: ff Edit issue ff Assign issue ff Log work system-issuetabpanels- plugin.xml System issue tab panels: Renders the various tabs on the View Issue page. Examples are: ff All tab panel ff Comment tab panel ff Work log tab panel ff Change history tab panel ff CVS tab panel system-issueviews-plugin. xml Renders the single issue view and the various search request views. Examples are: ff Single issue views: XML, Word, Printable ff Search views: XML, RSS, RSS (comments), Printable, Word, Full content, Excel (all fields), Excel (current fields), Charts Understanding the Plugin Framework 48 System plugin XML Functionality system-jql-function-plugin. xml Built-in JQL functions. system-keyboard-shortcuts- plugin.xml Built-in keyboard shortcuts. system-macros-plugin.xml JIRA's base system macros. system-project-plugin.xml System project panels: Renders the Browse Project, Browse Version, and Browse Component panels. system-projectroleactors- plugin.xml System project role actors: Built-in project role actors (User Role Actor and Group Role Actor) and the associated webwork actions. system- renderercomponentfactories- plugin.xml Renderer component factories plugin: Instantiates renderer components using the plugin system. Examples: Macro renderer, Link renderer, URL renderer, and so on. system-renderers-plugin.xml Built-in system renderers: ff Wiki-style renderer ff Default text renderer system-reports-plugin.xml Built-in system reports. system-top-navigation- plugin.xml Renders the content of the top navigation bar in JIRA. Has a collection of web-items and web-sections. system-user-format-plugin. xml Renders a user in JIRA differently at different places. system-user-profile-panels. xml Renders the panels on the User Profile page. system-webresources-plugin. xml System web resources: Includes static resources such as JavaScript files, style sheets, and so on. system-webwork1-plugin.xml System webwork plugin: Can be used to add custom webwork actions, which can also be done using plugins. system-workflow-plugin.xml System workflow conditions, functions, and validators. In addition to using these files as a starting point for JIRA plugin development, we might sometimes end up modifying these files to override the way JIRA works. Care must be taken to upgrade the changes during the time of a JIRA upgrade. Chapter 2 49 So that was a pretty lengthy introduction to the JIRA architecture! Let us quickly move on to the recipes in this chapter. Time to code!! Stable and core APIs So, we are in to coding. If you are familiar with JIRA plugin development or have been using them prior to JIRA 5, one thing you might have noticed is that there are quite a few versions of every plugin out there! There are not many plugin versions that work on all JIRA versions. You might also know the reason behind this: JIRA has been evolving a lot in terms of its APIs. With JIRA 5, Atlassian has introduced stable and core APIs, and with great interest, the development community read the following blog written by Rich Manalang: http://blogs.atlassian.com/2012/03/stable-apis-yes-we-have-them/ The earlier atlassian-jira dependency, which was used in JIRA plugins prior to JIRA 5, is now replaced by two different dependencies: jira-api and jira-core. jira-api (aka the stable API) is a set of classes or interfaces that will not be changed without prior notice. Atlassian will maintain binary compatibility for these classes and interfaces. jira-core, on the other hand, contains internal JIRA classes, and they may change without prior notice. Plugin developers are free to have dependency on these core classes, but will have to bear the risk of creating new plugin versions if those classes change across versions, even minor one's. To use stable APIs in your plugin, the following dependency needs to be added in to the pom.xml file: com.atlassian.jira jira-api ${atlassian.product.version} provided For core APIs, the dependency will be the following: com.atlassian.jira jira-core ${atlassian.product.version} provided Understanding the Plugin Framework 50 The old atlassian-jira dependency will still work as it creates a "shaded" Maven module that combines both jira-core and jira-api dependencies. It is, however, not advised to use atlassian-jira as you might be bringing in dependencies that you do not need. Before jumping into the next recipe, have a look at the JIRA API policy that you can find at https://developer.atlassian.com/ display/JIRADEV/Java+API+Policy+for+JIRA. Modifying Atlassian bundled plugins As we discussed earlier, more and more standard functionalities are pushed into bundled plugins as opposed to the JIRA core product. There is no better way to showcase the plugin architecture, I must admit! But that does make the life of high end users who want to modify those functionalities a bit difficult. Let me take a once simple scenario such as "I want to display the description before the issue details in the View Issue page." This used to be pretty easy, because all you needed to do was to modify the relevant JSP file and that's it! But now, the View Issue screen rendering is done by jira-view-issue-plugin and it is an Atlassian system plugin. Although the actual work is simple—we only need to modify the atlassian-plugin.xml file—making those changes effective is not as simple as editing the jira-view-issue-plugin-xxx.jar file from the JIRA_Home/.plugins/bundled- plugins folder. Let us see why! How to do it… The reason is pretty simple. All the system plugin JAR files under JIRA_Home/plugins/. bundled-plugins are overwritten by the original JAR files from the atlassian-bundled- plugins.zip file under the JIRA_Install_Directory/atlassian-jira/WEB-INF/ classes/ folder whenever JIRA is restarted. So, how do we override it? There is only one solution: modify the JAR files under the atlassian-bundled-plugins.zip file directly! Following are the steps to do this, and these steps are applicable to modifying any Atlassian bundled plugins: 1. Extract the atlassian-bundled-plugins.zip file under the atlassian-jira/ WEB-INF/classes folder to a separate directory. 2. Put your customized system plugin JAR in the place of the existing JAR file. You can either build the customized JAR file from its source or just extract the JAR, modify the, files and create it back. Chapter 2 51 3. Zip all the JARs back with the same name: atlassian-bundled-plugins.zip. 4. Restart JIRA. And we are done! Steps 1 to 3 can be done easily if you have a utility such as 7-Zip in Windows. For the example we considered, all we need to do is to modify the atlassian-plugin.xml file inside the jira-view-issue-plugin-5.x.jar file. The different modules on the View Issue page such as the Details module, Description module, and so on are web-panel plugin modules, and its order can be modified with the weight attribute. By default, details-module has weight 100 and description-module has weight 200. Change the order and it is as simple as that. How it works… As mentioned earlier, JIRA overwrites the JIRA_Home/plugins/.bundled-plugins folder with the contents from the atlassian-bundled-plugins.zip file during startup. Now that we have the modified ZIP file, its contents will overwrite what is in that folder. Care must be taken to migrate these changes over when JIRA is upgraded next! See also ff The Setting up the development environment recipe in Chapter 1, Plugin Development Process Converting plugins from v1 to v2 If you are moving to JIRA 4.x+ from JIRA 3.13.x or earlier versions, one of the important differences is the introduction of v2 plugins. While designing the upgrade to JIRA 4.x+, it makes perfect sense to sometimes migrate the plugins from v1 to v2, although it is not a mandatory step. In this recipe, we will see how to convert a version1 plugin to a version2 plugin. Getting ready There are a couple of questions we need to ask before the plugin is converted: ff Are all the packages used by the plugin available to OSGi plugins? This is very important because JIRA doesn't expose all the packages to OSGi plugins. Understanding the Plugin Framework 52 The list of packages exported and made available to the Plugins2 version can be found in the com.atlassian.jira.plugin. DefaultPackageScannerConfiguration class. ff Are all the components used by the plugin available to OSGi plugins? Similar to the previous question, we need to make sure the components are also exposed to the OSGi plugins. Unfortunately, there is no definite list provided by Atlassian for JIRA. To check if the components are available, use dependency injection. The plugin will fail on start up if the component is not available. How to do it… The actual conversion process of v1 plugins to v2 is easier than you think if the packages and the components that you have used in the plugin are available to the OSGi plugins. Following are the steps for conversion: 1. Add the plugins-version="2" attribute in the atlassian-plugin.xml file. This is probably the only mandatory step in the conversion process. You will be amazed to see that many of the plugins will work as it is! Once added, the plugin descriptor looks like the following code snippet: ..................... 2. Modify the source code if required. This includes migration to the new API if you are moving to a new JIRA version with API changes, working out the changes if some of the packages/components not exported to OSGi are used in the v1 plugin, and so on. 3. Customize the package imports and exports by defining them in the bundle manifest. You can do this by using the bundle instructions we saw while explaining the atlassian-plugin.xml file earlier in this chapter, or simply by adding the appropriate entries into the manifest file in your JAR. This is an optional step that you need to do only if you want to import packages from another plugin/bundle or you want to export some of your packages to make it available to other plugins. 4. Expose your custom plugin components to other plugins using the component module. You must set the public attribute to true in the component registered in your atlassian-plugin.xml file. That is, public="true". Chapter 2 53 You must import the components specifically if you want to use the components declared publicly in other plugins. Use the component-import module to do this: 5. You can also optionally add advanced Spring configurations by adding Spring Dynamic Modules (Spring DM configuration files of the format *.xml) under the META-INF/spring/ directory. The Spring DM loader will then load these files. The details of how to do this are outside the scope of this book. How it works… The v2 plugin JAR file created with the Atlassian descriptor containing the required modules goes through the following journey: 1. The plugin is loaded upon the startup of JIRA and JIRA identifies the new JAR. 2. DirectoryLoader checks whether the new plugin is version2 or version1. 3. If version2, it checks for the OSGi manifest entries, which you can enter in the MANIFEST.MF file. If found, the plugin is installed as an OSGi bundle and started. 4. If the OSGi manifest entries are not present, JIRA uses the bnd tool (http://www.aqute.biz/Code/Bnd) to generate the manifest entries and insert them into the MANIFEST.MF file. 5. JIRA then checks for the presence of an explicit atlassian-plugin-spring.xml file. If the file is present, the plugin is then deployed as an OSGi bundle, as in step 2. 6. If the atlassian-plugin-spring.xml file is absent, JIRA then scans the atlassian-plugin.xml file and converts the registered components and others into OSGi references or OSGi services and creates an atlassian-plugin- spring.xml file. 7. Once the atlassian-plugin-spring.xml file is created, the plugin is deployed as an OSGi bundle and installed into the Plugin Manager. JIRA thus gives us the flexibility to define our own custom OSGi manifest entries and references, or let JIRA do the dirty work by defining them appropriately in the plugin descriptor. See also ff The Deploying a JIRA plugin recipe in Chapter 1, Plugin Development Process ff The Creating a skeleton plugin recipe in Chapter 1, Plugin Development Process Understanding the Plugin Framework 54 Adding resources into plugins It is often required to add static resources such as JavaScript files, CSS files, and so on in to our plugins. To enable JIRA to serve these additional static files, they should be defined as downloadable resources. Getting ready A resource can be of different types. It is normally defined as a non-Java file that the plugin requires to operate. Examples of resources that you will come across during JIRA plugin development include, but are not restricted to, the following: ff Velocity (*.vm) files required to render a view ff JavaScript files ff CSS files ff Property files for localization How to do it… To include a resource, add the resource module to the atlassian-plugin.xml file. The resource module can be added as part of the entire plugin or can be included within another module, restricting it just for that module. The following are the attributes and elements available for the resource module and their uses: Name Description name The name of the resource. This is used by the plugin or module to locate a resource. You can even define a directory as a resource by adding a trailing slash (/). namePattern The pattern to use when loading a directory resource. type The type of the resource. Examples are: ff download: For resources such as CSS, JavaScript, images, and so on ff velocity: For Velocity files location The location of the resource within the plugin JAR. The full path to the file without a leading slash is required. When using—namePattern or pointing to directory resource—a trailing slash (/) is required. Chapter 2 55 Name Description property (key/ value) This is used to add properties as key-value pairs to the resource. It is added as a child tag to resources. For example: param (name/ value) This is used to add name/value pairs. It is added as a child tag to resources. For example: All you have to do is to add the resource tag to the atlassian-plugin.xml file, either at the plugin level or at a module level. The resource will then be available for use. The resource definition for an image will look as follows: A CSS file might looks as follows: Once the resource is defined in the plugin descriptor, you can use it anywhere in the plugin. Following is how you refer to the resource: 1. Let us consider that you have a directory referenced as follows: 2. A demoimage.gif file can be a reference in your Velocity template as follows: $requestContext.baseUrl/download/resources/${your_plugin_ key}:${module_key}/images/ demoimage.gif 3. A sample piece of code used in your plugin module looks as follows: In this case, com.jtricks.demo is the plugin key and demomodule is the module key. More details and available values for the param element can be found at https://developer.atlassian.com/display/ JIRADEV/Downloadable+Plugin+Resources. Understanding the Plugin Framework 56 Adding web resources into plugins The web resources plugin module, like the resource module we just saw, allows for the defining of downloadable resources. The difference is that the web resources are added at the top of the page in the header with the cache-related headers set to never expire. An additional advantage of using the web resources module is that we can specify the resources to be included in specific contexts within the application. How to do it… The root element for the web resource plugin module is web-resource. It supports the following attributes: Name Description key The only mandatory attribute. This should be unique within the plugin. state This indicates whether the plugin module should be disabled by default or not. The default value enabled. value="disabled" will keep the plugin disabled at startup. i18n-name-key The localization key for the human-readable name of the plugin module. name The human-readable name of the web resource. system This indicates whether the plugin module is a system plugin module. This property is available only to non-OSGi plugins. The default value is false. The following are the key elements supported: Name Description description The description of the module, i18n keys supported. resource All the resources to be added as web resources. (See the Adding resources into plugins recipe.) dependency This is used to define dependency on the other web-resource modules. The dependency should be defined as pluginKey:web- resourceModuleKey, for example: com.jtricks.demoplugin:demoResource context This defines the context where the web resource is available. condition This adds conditions to define whether the resource should be loaded or not. (See the Adding conditions to web fragments recipe.) transformation This makes a particular transformer available to the web resource. (See the Developing a web resource transformer recipe.) Chapter 2 57 We can define the web-resource module by populating the attributes and elements appropriately. An example would look as follows: Demo Plugin for web-resources 1.0 How it works… When a web-resource module is defined, it is available for you in the plugin just like your downloadable plugin resources. As mentioned earlier, these resources will be added to the top of the page in the header section. In your action class or servlet, you can access these resources with the help of webResourceManager. Inject the Manager class into your constructor and you can then use it to define the resource, shown as follows: webResourceManager.requireResource("com.jtricks.demoplugin: demoresource"); The argument should be pluginKey:web-resourceModuleKey. By default, all the resources under the web-resource module are served in batch mode, that is, in a single request. This reduces the number of HTTP requests from the web browser. There's more... Before we wind up this recipe, it is probably a good idea to identify the available contexts for web resources, and also to see how we can turn off the batch mode while loading resources. Understanding the Plugin Framework 58 Web resource contexts Following are the available web resource contexts for all Atlassian products: ff atl.general: Available everywhere except administration screens ff atl.admin: Available on administration screens ff atl.userprofile: Available on user profile screens ff atl.popup: Available on browser pop-up windows JIRA supports a lot more contexts, and the full list can be found at https://developer.atlassian.com/display/JIRADEV/ Web+Resource+Plugin+Module. You can have multiple contexts added, as follows: atl.general atl.admin You can even define a custom context here, for example: com.jtricks.democontext You can then load these resources into the pages by adding the following line in your page: $webResourceManager.requireResourcesForContext("com.jtricks. democontext") Turning off batch mode As mentioned earlier, the resources are loaded in one batch to reduce the number of HTTP requests from the browser. But if you want to switch off the batch mode for some reason, it can be achieved in two ways: ff You can do a system-wide switch off of batch mode by adding the property plugin. webresource.batching.off=true into jira-config.properties. ff It can be turned off on individual resources by adding a param element as follows: Chapter 2 59 See also ff The Adding resources into plugins recipe Building JIRA from source One of the best things about JIRA, if you have a valid license, is that you get to see the source code. You can see it, modify it, break it… err? In this recipe we will modify it, because you have the license to do it! Getting ready Following are some of the prerequisites prior to building JIRA from the source: ff A valid JIRA license to get access to the source code. ff An environment with JDK 1.5 or higher for JIRA 4.2 and lower versions. JDK 1.6.x is required for JIRA 4.3 and above versions. ff You will need both Maven 1 and Maven 2 if you are building versions prior to JIRA 4.3. Download Maven version 1.0.x and 2.1.x from http://maven.apache.org/. JIRA 4.3 and above versions only need Maven 2.1.0. You need both Maven 1 and Maven 2 for versions prior to JIRA 4.3, because Maven 1 is required to build the JIRA source and Maven 2 is required to build plugins for JIRA. JIRA has bundled plugins that need to be built along with JIRA, and so Maven 2 is also a must. Maven 2.1.0 and above is required for the plugin development process. How to do it… Let us see the steps to build JIRA WAR from the source for versions prior to JIRA 4.3: 1. Configure Maven 1.0.x: a. Extract the Maven 1.0.x version downloaded earlier to a directory, which we will now refer to as MAVEN_INSTALL_DIR. b. Download an Atlassian patched version of Ant JAR from http:// confluence.atlassian.com/download/attachments/185729661/ ant-optional-1.5.3-1.jar?version=1&modificationDa te=1276644963420. c. Copy it to MAVEN_INSTALL_DIR/maven-1.0/lib. Understanding the Plugin Framework 60 d. Set the MAVEN_HOME environment variable, which will be MAVEN_INSTALL_ DIR/maven-1.0. e. Add Maven's bin directory to the path variable. 2. Configure Maven 2.1.x. If you have already set up your development environment using Atlassian plugin SDK, you can skip this test as it comes along with a bundled Maven 2.x: a. Install Maven 2.1.x as per the instructions at http://maven.apache. org/download.html. b. Configure settings.xml by following the example settings.xml file provided by Atlassian at https://developer.atlassian.com/ display/DOCS/Example+settings.xml. 3. Download the JIRA source ZIP file from https://my.atlassian.com/download/ source/jira. 4. Extract the JIRA source to a directory, which we will call JIRA_DIR. 5. Go to the jira subdirectory, that is, JIRA_DIR/jira. 6. Run the following command to create an open WAR file: maven war:webapp If you want to create a closed WAR file, execute the following command: maven war:war See http://maven.apache.org/maven-1.x/plugins/war/ goals.html for more Maven WAR goals. 7. Confirm that the WAR file is created properly. The following are the steps to create the WAR file on JIRA 4.3 and higher versions: 1. Configure Maven 2.1.0. 2. Download and install the required third-party libraries, as these libraries are not available in the public Maven repositories: a. Download the correct version of the JAR files, as mentioned in the following table: activation javax.activation:activation 1.0.2 jms javax.jms:jms 1.1 jmxri com.sun.jmx:jmxri 1.2.1 jmxtools com.sun.jdmk:jmxtools 1.2.1 jndi jndi:jndi 1.2.1 Chapter 2 61 jta Jta:jta 1.0.1B mail javax.mail:mail 1.3.2 ojdbc6 com.oracle:ojdbc6 11.2.0.2.0 b. Install them to the local Maven repository using the Maven install command, shown as follows: mvninstall:install-file -DgroupId=javax.activation -DartifactId=activation -Dversion=1.0.2 -Dpackaging=jar -Dfile=activation-1.0.2.jar mvninstall:install-file -DgroupId=javax.jms -DartifactId=jms -Dversion=1.1 -Dpackaging=jar -Dfile=jms-1.1.jar mvninstall:install-file -DgroupId=com.sun.jmx -DartifactId=jmxri -Dversion=1.2.1 -Dpackaging=jar -Dfile=jmxri.jar mvninstall:install-file -DgroupId=com.sun.jdmk -DartifactId=jmxtools -Dversion=1.2.1 -Dpackaging=jar -Dfile=jmxtools.jar mvninstall:install-file -DgroupId=jndi -DartifactId=jndi -Dversion=1.2.1 -Dpackaging=jar -Dfile=jndi.jar mvninstall:install-file -DgroupId=jta -DartifactId=jta -Dversion=1.0.1 -Dpackaging=jar -Dfile=jta-1_0_1B-classes. jar mvninstall:install-file -DgroupId=javax.mail -DartifactId=mail -Dversion=1.3.2 -Dpackaging=jar -Dfile=mail.jar mvninstall:install-file -DgroupId= com.oracle:ojdbc6 -DartifactId=ojdbc6 -Dversion=11.2.0.2.0 -Dpackaging=jar -Dfile=ojdbc6.jar 3. Extract the JIRA source archive to a local directory, which we will call JIRA_DIR. 4. Navigate to the extracted subdirectory with the name atlassian-jira-X.Y- source where X.Y is the version. 5. Run build.bat if on Windows, or build.sh if on Linux or Mac. 6. Confirm that the WAR file is created properly under the JIRA_DEV/jira-project/ jira-distribution/jira-webapp-dist/target subdirectory. How it works… As you have seen, the process is pretty straightforward and Maven—the magician—does the actual build. JIRA ships with the project.xml or pom.xml files if in version 4.3 and above, called the Project Object Model (POM), which is used by Maven to build the WAR file. Understanding the Plugin Framework 62 You will be able to find the JIRA dependencies inside the project.xml or pom.xml files. Maven will first build the dependencies and then build the JIRA WAR file using them. The only key thing here is to set up Maven correctly. There are a couple of issues normally observed while building JIRA WAR, both related to Maven. Maybe it is worth touching upon them before we move ahead: ff An error can occur while downloading dependencies due to the java.net. ConnectException: Connection timed out: connect exception. If you encounter this, make sure that the Maven proxy settings are configured properly. If they are already configured and still you are getting the error, try disabling your antivirus! ff A failed to resolve artifact… error can occur. Building JIRA 4.0 fails to download javaxjms jar. In such cases, download the JAR manually and install them into the local repository using mvninstall. mvninstall:install-file -Dfile= -DgroupId= -DartifactId= -Dversion= -Dpackaging= If using version 4.3 and above, refer to step 2 in the recipe where the relevant mvninstall commands are given. Once the WAR file is created, deploy it into a supported application server, and enjoy the power of JIRA! There's more… Along with the JIRA source, we have access to the source code of some of the JIRA dependencies from Atlassian. You might want to build them separately if you ever want to modify their behavior. Building JIRA dependencies Similar to JIRA, the dependent projects also use Maven. But it uses Maven 1 in some cases and Maven 2 in others. You can determine whether the dependency uses Maven 1 or Maven 2 by checking its POM by looking in the root directory. If the file is named project.xml, it uses Maven 1, and if the file is named pom.xml, it uses Maven2. Simple, right? Use the following command to generate the JAR file for a dependency if it uses Maven 1: maven jar Chapter 2 63 For dependencies with Maven 2, use the following: mvn package See also ff The Setting up the development environment recipe in Chapter 1, Plugin Development Process Adding new webwork actions to JIRA Most of the time, plugin developers will find themselves writing new actions in JIRA to introduce new functionality. Usually these actions are invoked from new web-item links configured at different places in the UI. It could also be from customized JSPs or other parts of the JIRA framework. New actions can be added to JIRA with the help of the webwork plugin module. Getting ready Before we start, it probably makes sense to have a look at the webwork plugin module. Following are the key attributes that are supported: Name Description key A unique key within the plugin. It will be used as the identifier for the plugin. class This will be java.lang.Object as the real logic will reside in the action, Class. i18n-name-key The localization key for the human-readable name of the plugin module. name The human-readable name of the webwork action. The following are the key elements supported: Name Description description The description of the webwork module. actions This is where we specify the webwork1 actions. A webwork module must contain at least one action element. It can have any number of actions. Understanding the Plugin Framework 64 For each webwork1 action, we should have the following attributes populated: Name Description name The fully qualified name of the action class. The class must extend com.atlassian.jira.action.JiraActionSupport. alias An alias name for the action class. JIRA will use this name to invoke the action. The following element is supported for the webwork1 action: Name Description view This delegates the user to the appropriate view based on the output of the action. This element has a name attribute that maps to the return value of the action class. Now that you have seen the attributes and elements supported, we can have a look at a sample webwork module before proceeding to create one: /templates/input.vm /templates/joy.vm /templates/tears.vm How to do it… Let us now aim at creating a sample webwork action. For the following example, we can create an action that takes a user input, prints it out in the console, and displays it on the output page after modifying the input. In order to do this, perform the following steps: 1. Add the new webwork action module into your atlassian-plugin.xml file. Let us say we add the same aforementioned snippet. 2. Create the demoaction action class under the com.jtricks package. The class must extend com.atlassian.jira.action.JiraActionSupport. Chapter 2 65 3. Identify the parameters that you need to receive from the user. Create private variables for them with the name exactly similar to that of the related HTML tag. In our example, we need to take a user input. Let us say it is the name of the user. The HTML code in the input view (in our case, /templates/input.vm) will be as follows: Name: So, we need to create a string variable of the name userName in our action class. 4. Create setter methods for the variables that are used to get values from the input view. In our example, we retrieve the userName name from the input view and process it in the action class. So we need to create a setter method for that will look like the following code snippet: public void setUserName(String userName) { this.userName = userName; } 5. Identify the parameter that needs to be printed in the output page. In our case, we will print modifiedName in the output page. 6. Create getter methods for the parameters to be printed. Velocity or JSPs will invoke the getter methods to retrieve the value from the action class. For our example, we have a getter method for modifiedName, which looks as follows: public String getModifiedName() { return modifiedName; } 7. Override the methods of interest. This is where the actual logic will fit it. It is entirely up to the plugin developer to determine which methods are to be overridden. It totally depends on the logic of the plugin. The three main methods of interest are the following. You can however completely omit these methods and write your own commands and related methods—more of which we will see at the end of the recipe. ‰‰ doValidation: This is the method where the input validation happens. Plugin developers can override this method and add our own bits of custom validations. ‰‰ doExecute: This is where the action execution happens. When the input form is submitted, the doExecute method is called if there are no validation errors. All the business logic is done here and the appropriate view name is returned, based on the execution result. Understanding the Plugin Framework 66 In our example, we use the following method to modify the input string: if (TextUtils.stringSet(userName)) { this.modifiedName = "Hi,"+userName; return SUCCESS; } else { return ERROR; } ‰‰ doDefault: This method is invoked when the default command is used. In our example, DemoAction!default.jspa will invoke the doDefault method. In our example, we use the following method to redirect the user to the input page: return "input"; 8. Create the Velocity template for the input view. The input view, in our example, uses the template /templates/input.vm. Add the HTML code of the input text within a form that will invoke the action DemoAction:

My Input Form



Name:

Note that $requestContext.baseUrl gets you the base URL of JIRA and will be useful if your JIRA instance has a context path. A $requestContext variable is populated in to the context by the JIRAActionSupport, which we have overridden. 9. Create the success view to print the modifiedName in /templates/joy.vm:

Output



$modifiedName 10. Create the error view in /templates/error.vm:

Oh No, Error!



Please specify a user name! 11. Package the plugin and deploy it. 12. Point your browser to ${jira_base_url}/secure/DemoAction!default.jspa. Enter a name and submit the form to see it in action! The example given in this recipe is just for the sake of understanding how the webwork action works. Chapter 2 67 How it works… It is probably worth utilizing this section to see how the flow works in our example. Let us see it happening as a step-by-step process: 1. When ${jira_base_url}/secure/DemoAction!default.jspa is invoked, the plugin framework looks for the DemoAction action registered in the atlassian- plugin.xml file and identifies the command and view associated with it. 2. Here, the default command is invoked, and therefore the doDefault method in the action class is executed. 3. The doDefault method returns the view name as input. 4. The input view is resolved as input.vm, which presents the form to the user. 5. On the form, webwork populates the userName value in the action class using the setter method. In the execution flow, first the doValidation method is invoked. If no error is there, which is the case in our example, it invokes the doExecute method. If there is any error in doValidation, the execution stops and the input (current) view is shown. You can print the error messages appropriately on the input view, if there are any. See the webwork1 documentation for details. 6. In doExecute, the input string userName is modified and assigned to modifiedName. Then success is returned. Here, if there are any errors (like the userName is empty), you can handle it to return the error view instead of success. 7. The success view is resolved as joy.vm where the modifiedName is printed. $modifiedName will invoke the getModifiedName() method to print the modified name. If an error is returned, the view is resolved as error.vm and the appropriate error message is shown! In this way, we can write complex actions in JIRA that can be used to customize a lot of the aspects of JIRA. There's more… It is also possible to add custom commands to the webwork actions, in addition to the doExecute and doDefault methods. This enables the developer to invoke the action using user-friendly commands, say ExampleAction!hello.jspa. Understanding the Plugin Framework 68 Adding new commands to the action You can write new methods such as doCommand() in your action class and just invoke them by ExampleAction!command.jspa. For example, write a method doHello() in your action class and you can invoke it by calling ExampleAction!hello.jspa. You can also define different user-friendly aliases for these new commands and can have separate views defined for them. In this case, you will have to modify the action tag to include a command tag in addition to the views. The following is a short example of how to do this. The atlassian-plugin.xml file should be modified to include the new command under the action: /templates/input.vm /templates/joy.vm /templates/tears.vm /templates/hello.vm /templates/tears.vm Here, you can invoke the method by calling DemoHello.jspa, and returning success will take the user to /templates/hello.vm instead of /templates/joy.vm. See also ff The Deploying a JIRA plugin recipe in Chapter 1, Plugin Development Process Extending a webwork action in JIRA There are so many user stories for this one! How do you override some of the JIRA built-in actions? How do you do some additional stuff in the JIRA built-in action? Like doing some crazy things immediately after logging work on a ticket, or doing some innovative validations on some of those actions. Extending the existing JIRA action is an answer to all these questions. Let us see in detail how to do that. How to do it… Extending a JIRA action is done with the help of the webwork plugin module. Most of it is very similar to writing new webwork actions. Chapter 2 69 Let us take the case of the AddProject action. What should we do if we need to extend this action? Say, to do some additional validation during the creation of a project? Let us consider an example where we want to prevent users from creating project keys using reserved words. The following are the steps to do this: 1. Identify the action to be overridden by looking up the actions.xml file under WEB-INF/classes in your JIRA installation directory. In our case, AddProject is the action class that does the creation of the work log on an issue: /secure/admin/views/addproject.jsp /secure/admin/views/addproject.jsp This snippet defines the action class and the related views that are using JSP files. 2. Determine whether we need to override the action or just modify the JSP files. In our example, let us do some extra validation. 3. Add the webwork plugin module in the atlassian-plugin.xml file: /secure/admin/views/addproject.jsp /secure/admin/views/addproject.jsp Note the change in action class name. We can also change the JSP files if that is needed. Most importantly, the alias name should be exactly the same as the action alias name in the actions.xml file. In this case, the alias name is AddProject. 4. Create the action class com.jtricks.MyAddProject. This can be any name. Understanding the Plugin Framework 70 5. We can do the full action class implementation in MyAddProject. However, in most cases, you might just need to override some methods of the existing action class, as in our example. If so, just extend the original action class shown as follows: public class MyAddProject extends AddProject{ 6. Add the appropriate constructor to carry out a dependency injection and to call the superclass constructor. Eclipse, or the IDE you use, will usually prompt this. If you need any other Manager classes to add your extra logic, inject them as well in the constructor. 7. Override the methods you want. In our example, we need to do extra validations. Let us see how to add a validation to check if the key is a reserved word or not: @Override protected void doValidation() { super.doValidation(); if (TextUtils.stringSet(getKey()) && Arrays. asList(reservedKeys).contains(getKey())) { addErrorMessage(getKey() + " is a reserved word. Please use a different key"); } } Here we check if the key is a valid string and is not in the reservedKeys array. Let us assume reservedKeys is a constant array, shown as follows: private static final String[] reservedKeys = new String[] { "YES", "NO", "STUPID" }; 8. Package the plugin and deploy it. 9. Create a project with one of the reserved words and see how JIRA behaves! How it works… The key aspect of extending an existing action is to use the same alias name in your webwork plugin module. JIRA registers all the actions in an actions.xml file and overwrites them with the actions in plugins, if the same alias name is found. In this case, JIRA registers the class com.jtricks.MyAddProject for the AddProject action instead of the original project.AddProject class. Chapter 2 71 If you try to create a project with the reserved word as the key, after the plugin is deployed, you will see an error as shown in the following screenshot: You cannot override an action in another plugin, even if you use the same alias name. Some of the actions in JIRA might be in the bundled plugin, and so you will not be able to override them using this technique. Also, look out for any third-party plugins, which might override the same action! See also ff The Adding new webwork actions to JIRA recipe Capturing plugin installation/uninstallation events We have seen plugins that are great in terms of functionality but come with a big list of configuration steps. Its much like a treadmill; a great asset but very hard to assemble! Is there a way we can handle these configurations automatically (like creating custom fields, adding options, creating listeners, services, and so on), when the plugin is installed? The answer is "Yes". Understanding the Plugin Framework 72 How to do it… It is simple. Really! Let us look at creating a custom field automatically during a plugin installation and deleting it while uninstalling. The same logic applies as for the enabling/disabling of the plugin. All you need to do is two simple steps: ff Write an event listener. We will use the atlassian-event library here. ff Implement the Spring interfaces InitializingBean and DisposableBean to capture the plugin lifecycle events. Writing an event listener in JIRA is quite easy. Just perform the following steps: 1. Import the EventPublisher instance used to register events. You can do this by adding the following component in atlassian-plugin.xml: 2. Now, instantiate our listener class as follows: A Listener for plugin lifecycle events So, what's the deal with a listener here? Basically, Atlassian plugins are implemented as Spring dynamic modules and the atlassian-plugin.xml file is transformed into a Spring XML bean configuration before it is loaded by JIRA. As our listener is registered as a component module, it will become a Spring bean when loaded, and hence we can use the Spring interfaces—InitializingBean and DisposableBean—to capture plugin lifecycle events. Whenever the component (here PluginListener) is registered, that is, during the enabling or installation of the plugin, the afterPropertiesSet() method from InitializingBean is invoked. Similarly, when the component is unregistered, during plugin disabling or uninstallation, the destroy() method from DisposableBean is invoked. Our listener will look like the following code snippet: public class PluginListener implements InitializingBean, DisposableBean { @Override public void destroy() throws Exception { //Handle plugin disabling or un-installation here } @Override Chapter 2 73 public void afterPropertiesSet() throws Exception { //Handle plugin enabling or installation here } } That's it! How it works… So, how does it help during plugin configurations? Suppose we have a plugin that needs a text custom field with a predefined name for its functionality. Users will have to configure this manually when they install a plugin and remove it when they uninstall it. This is also prone to manual errors. Why not do these in the plugin itself? Let us create a text custom field during the plugin enabling/installation and associate it with a screen. We will also remove this when the plugin is disabled or uninstalled. The code is an extension to the previous one and is self-explanatory. The following is how it goes: public class PluginListener implements InitializingBean, DisposableBean { private static final String TEST_TEXT_CF = "Test Text CF"; private final CustomFieldManagercustomFieldManager; private final FieldScreenManagerfieldScreenManager; public PluginListener(CustomFieldManagercustomFieldManager, FieldScreenManagerfieldScreenManager) { this.customFieldManager = customFieldManager; this.fieldScreenManager = fieldScreenManager; } @Override public void destroy() throws Exception { //Get the already installed custom field by name CustomFieldcField = this.customFieldManager. getCustomFieldObjectByName(TEST_TEXT_CF); //Remove if not null if (cField != null) { this.customFieldManager.removeCustomField(cField); } } @Override public void afterPropertiesSet() throws Exception { Understanding the Plugin Framework 74 //Create a list of issue types for which the custom field needs to be available ListissueTypes = new ArrayList(); issueTypes.add(null); //Create a list of project contexts for which the custom field needs to be available List contexts = new ArrayList(); contexts.add(GlobalIssueContext.getInstance()); //Add custom field CustomFieldcField = this.customFieldManager. createCustomField(TEST_TEXT_CF, "A Sample Text Field", this. customFieldManager.getCustomFieldType("com.atlassian.jira.plugin. system.customfieldtypes:textfield"), this.customFieldManager.getCustomFieldSearcher("com.atlassian.jira. plugin.system.customfieldtypes:textsearcher"), contexts, issueTypes); // Add field to default Screen FieldScreendefaultScreen = fieldScreenManager. getFieldScreen(FieldScreen.DEFAULT_SCREEN_ID); if (!defaultScreen.containsField(cField.getId())) { FieldScreenTabfirstTab = defaultScreen.getTab(0); firstTab.addFieldScreenLayoutItem(cField.getId()); } } } As mentioned earlier, when the plugin is installed or enabled, the afterPropertiesSet() method from InitializingBean is invoked. In our example, we create a custom field, create a context for it, and add it into a screen. Similarly, when the plugin is uninstalled or disabled, the destroy() method from DisposableBean is invoked. In our example, we delete the custom field in that method. It is not wise to delete a custom field during uninstallation as it will also delete all the values associated with it, and this is not an irreversible action. Somebody might accidently disable a plugin! The example is just to show the power of such a simple code. Chapter 2 75 Following is a screenshot of how the field will look once the plugin is installed: Try uninstalling, installing it back, disabling, enabling it back, and so on to see the custom field disappearing and appearing back! See also ff The Deploying a JIRA plugin recipe in Chapter 1, Plugin Development Process 3 Working with Custom Fields In this chapter, we will cover: ff Writing a simple custom field ff Custom field searchers ff Dealing with custom fields on an issue ff Programming custom field options ff Overriding validation of custom fields ff Customizing the change log value ff Migrating from one custom field type to another ff Making custom fields sortable ff Displaying custom fields on subtask columns ff User and date fields ff Adding custom fields to notification mails ff Adding help text for a custom field ff Removing the "none" option from a select field ff Making the custom field project importable ff Changing the size of a text area custom field Working with Custom Fields 78 Introduction For an issue tracking application, the more details you can provide about an issue, the better. JIRA helps by giving us some standard issue fields that are most likely to be used when creating an issue. But what if we need to capture additional information such as the name of the reporter's dad or something else that is worth capturing, perhaps the SLA or the estimated costs? For this, we can make use of custom fields. JIRA comes with a group of predefined custom field types. These include types such as number field, user picker, and so on, which are most likely to be used by JIRA users. But as you become a power user of JIRA, you might come across the need for a customized field type. That is where people start writing custom field plugins: to create new field types or custom searchers. We will use this chapter to learn more about custom fields. Writing a simple custom field In this recipe, we will see how to write a new custom field type. Once created, we can create a number of custom fields of this type on our JIRA instance that can then be used to capture information on the issues. New custom field types are created with the help of the customfield-type module. The following are the key attributes supported: Name Description key This should be unique within the plugin class Must implement the com.atlassian.jira.issue. customfields.CustomFieldType interface i18n-name-key The localization key for the human-readable name of the plugin module name Human-readable name of the web resource The following are the key elements supported: Name Description description Description of the custom field type. resource type="velocity" Velocity templates for the custom field views. Chapter 3 79 Name Description valid-searcher From JIRA 5.2, you can define a searcher in the custom field definition itself. This element is most useful when we have a new custom field type that wants to make use of JIRA's core searchers. It has two attributes: ff package: The key of the Atlassian plugin where the custom field searcher resides ff key: The module key for the custom field searcher Getting ready Before we start, create a skeleton plugin. Next, create an Eclipse project using the skeleton plugin, and we are good to go! How to do it... In this recipe, let us look at an example custom field type to ease understanding. Let us consider the creation of a read-only custom field that stores the name of the user who edited the issue the last time. It is simple in functionality and enough to explain the basic concepts. The following are the major steps to complete: 1. Modify the atlassian-plugin.xml file to include the customfield-type module. Make sure the appropriate class names and views are added. For our example, the modified atlassian-plugin.xml file will look as follows: Read Only User CF Description 2. Make sure the key is unique inside the plugin. 3. Implement the class. As mentioned in the introduction, the class must implement the com.atlassian.jira.issue.customfields.CustomFieldType interface. If you do this, make sure you implement all the methods in the interface. Working with Custom Fields 80 An easier way is to override some of the existing custom field implementations, which are similar to the type you are developing. In such cases, you will only need to override certain methods or maybe just modify the Velocity templates! The details on existing implementations can be found at the Javadocs for the CustomFieldType interface. NumberCFType, DateCFType, UserCFType, and so on are some useful examples. In our example, the class is com.jtricks.ReadOnlyUserCF. Now, our field type is nothing but a text field in essence, so it makes sense to override the already existing GenericTextCFType. This replaces the TextCFType class we used to use in JIRA 4. The class will look as follows: public class ReadOnlyUserCF extends GenericTextCFType { protected ReadOnlyUserCF(CustomFieldValuePersist er customFieldValuePersister, GenericConfigManager genericConfigManager, JiraAuthenticationContext authContext) { super(customFieldValuePersister, genericConfigManager); this.authContext = authContext; } As you can see, the class extends the GenericTextCFType class. We perform a constructor injection to call the superclass constructor. All you need to do is add the required components as arguments in the public constructor of the class, and Spring will inject an instance of those components at runtime. Here, JiraAuthenticationContext is injected, in addition to the components required by the superclass, as we use it in our implementation. As you can see, authContext is an argument that is injected and is assigned to a class variable with the same name for using it later in the various methods. 4. Implement/override the methods of interest. As mentioned earlier, implement all the required methods if you are implementing the interface directly. In our case, we extend the GenericTextCFType class, and so we only need to override the selected methods. The only method that we override here is the getVelocityParameters method, where we populate the Velocity parameters with additional values. In this case, we add the current user's name. We will later use these parameters in the Velocity context to generate the views. The same method is used in creating different views in different scenarios, that is, create, edit, and so on. The following is the code snippet: @Override public Map getVelocityParameters(Issue issue, CustomField field, FieldLayoutItem fieldLayoutItem) { Chapter 3 81 Map params = super.getVelocityParameters(issue, field, fieldLayoutItem); params.put("currentUser", authContext.getLoggedInUser(). getName()); return params; } 5. Create the templates defined in the atlassian-plugin.xml file. The templates should be written in the way you want the fields to appear in different scenarios. If you take a closer look, we have defined four Velocity resources but using only two Velocity template files, as the view-readonly-user.vm template file is shared across view, column-view, and xml resources. In this example, we only need to show the readonly field in all the three mentioned cases, and so the template will look as follows: $!value Here we display the existing custom field value of the issue. This code uses Velocity syntax, the details of which can be found at http://velocity.apache.org/engine/devel/ developer-guide.html. 6. The edit template should be a read-only text field with id as the custom field's ID, as JIRA uses this to store values back into the database when the issue is edited. The template looks as follows: #customControlHeader ($action $customField.id $customField.name $fieldLayoutItem.required $displayParameters $auiparams) #customControlFooter ($action $customField.id $fieldLayoutItem. fieldDescription $displayParameters $auiparams) This is very similar to the edit template used by JIRA's text field. Here we use the field currentUser, which we added into the Velocity context in step 4. The value of the text field is set as $currentUser. The ID of the field is $customfield.id and the readonly attribute is present to make it read only. Also notice the control header and footer, which provide the usual look and feel. 7. Package the plugin and deploy it! Remember, more complex logic and beautifications can go into the class and Velocity templates. As they say, the sky is the limit! Working with Custom Fields 82 8. Once the plugin is installed, it is available under Administration | Issue Fields | Custom Fields. How it works… Once the plugin is installed, it is available under Administration | Issue Fields | Custom Fields. Create a new custom field of the type we just created and map it into the appropriate issue types and projects. Also add the fields to the appropriate screens. Once done, the field will be available on the issue in the appropriate places. In our example, whenever an issue is edited, the name of the user who edited it is stored in the custom field. More details on adding a custom field can be found at http://confluence.atlassian.com/display/JIRA/ Adding+a+Custom+Field. There's more… You might have noticed that we added only one parameter in the Velocity context, that is, currentUser, but we have used $value in the view template. Where does this variable value come from? JIRA already populates the custom field Velocity contexts with some existing variables in addition to the new ones we add. value is just one such variable among them and the full list can be found at https://developer.atlassian.com/display/JIRADEV/Custom+fi eld+Velocity+context+unwrapped. You may notice that authContext is already available in the Velocity context, and so we could have implemented this example by getting the current user in the Velocity template itself instead of injecting the JiraAuthenticationContext in the constructor of the class and getting the currentUser variable from it in the class. But we have done that just for the purpose of explaining the example. Chapter 3 83 See also ff The Creating a skeleton plugin recipe in Chapter 1, Plugin Development Process ff The Deploying a JIRA plugin recipe in Chapter 1, Plugin Development Process Custom field searchers Writing the custom field type is one thing, but making it available to one of JIRA's most powerful functionalities—that is, search—is another! When you create the custom field, you can associate the searcher to be used along with it. In most cases, you wouldn't need a custom searcher. Instead, you can use the built-in custom field searchers in JIRA itself. The list includes, but is not restricted to, text field searcher, date searcher, number searcher, user searcher, and so on. The first step, of course, is to determine what kind of searcher your new field needs. For example, a text field can easily be searched with a text searcher. A number field can be searched with a number searcher or a number range searcher. You might even want to extend one of these searchers to add some extra functionality, like some special conditions or hacks you want to introduce! Yeah, you know what I mean. JIRA has defined the text searcher for its system custom fields as follows: Search for values using a free text search. As you can see, it makes use of the customfield-searcher module. The custom fields that are searchable using this free text searcher should be added under the valid-customfield-type tag. Working with Custom Fields 84 An alternative way of doing this is to use the valid-searcher tag in the customfield-type module! The following are the key attributes supported by the customfield-searcher module: Name Description key This should be unique within the plugin class Must implement the com.atlassian.jira.issue. customfields.CustomFieldSearcher interface i18n-name-key The localization key for the human-readable name of the plugin module name Human-readable name of the web resource The following are the key elements supported by the customfield-searcher module: Name Description description Description of the custom field searcher module. resource type="velocity" Velocity templates for the custom field searcher views. valid- customfield-type Defines the custom field types this searcher can apply to. It has two attributes: ff package: The key of the Atlassian plugin where the custom field resides ff key: The module key for the custom field type Let's look in detail at how to define a searcher for the custom field we wrote in the previous recipe. Getting ready Make sure you have created the read-only user custom field (com.jtricks. ReadOnlyUserCF) using the previous recipe. Chapter 3 85 How to do it… As usual, we will do it as a step-by-step procedure: 1. Add the customfield-searcher module into the atlassian-plugin.xml file. In our example, the field is a read-only text field that holds the username, and so it makes sense to use the existing TextSearcher class instead of writing a new searcher class. The module will look as follows: Search for Read Only User using a free text search. Here we use com.atlassian.jira.issue.customfields.searchers. TextSearcher, which implements the com.atlassian.jira.issue. customfields.CustomFieldSearcher interface. If we need to write custom searchers, the appropriate class should appear here. We also need to define the Velocity templates for edit and view scenarios. 2. Implement the custom field searcher class. In our case, we can skip this step, as we are going with the already implemented class TextSearcher. Even if we are implementing a custom searcher, it might be wise to extend an already existing searcher class and override only the methods of interest to avoid implementing everything from scratch. Having said that, it is entirely up to the developer to give a brand new implementation. The only mandatory thing to note is that the searcher class must implement the com.atlassian.jira.issue. customfields.CustomFieldSearcher interface. 3. Write the Velocity templates. For a custom field searcher, there are two views—edit and view—both of which will appear on the issue navigator. The edit template is used when the filters are created/edited. The view template is used when the filter is viewed or the search results are viewed by clicking on View and Hide (Search from JIRA 4.3) on the issue navigator. Working with Custom Fields 86 In our example, we have used the in-built JIRA templates, but it is perfectly fine to give a custom implementation of these templates. 4. Make sure the valid-customfield-type tags are correctly entered. There is a basic (but very common) error you might make here. The package attribute here refers to the Atlassian plugin key where the custom field resides and not the Java package where the searcher class resides! Just to make it clear, the Atlassian plugin key is the key in the first line of your atlassian-plugin.xml file, which is com. jtricks in our case: This package (plugin key) along with the custom field key (readonly-user in this case) will point to the right custom field. This would also mean that you could have the same readonly-user as the custom field key in another plugin with a different plugin key! 5. Package the plugin and deploy it. If you are using JIRA 5.2+, and if you are going to use a JIRA core searcher in the read-only user custom field we created earlier, the steps we just discussed can be replaced by one simple step: 1. Identify the searcher to use and add it using the valid-searcher tag under the customfield-type module. In our case, we will select textsearcher. The updated atlassian-plugin.xml definition will look like the following code snippet: Read Only User CF Description Notice the new valid-searcher tag and how it makes it a lot simpler! But if you want to extend a searcher or write your own searcher, please follow the steps we discussed originally. Chapter 3 87 How it works… Once the custom field type is associated with a searcher using the customfield-searcher module or the valid-searcher element, you will see it appear in the searcher drop-down when a custom field of that type is created. For any existing custom fields, the searcher can be defined or modified using the edit operation. Once the searcher is changed, a reindexing must be done for the changes to be effective. It is possible to have more than one custom field using the same searcher. This can be achieved by defining more than one custom field using the valid-customfield-type element in the customfield-searcher module, or by using the same searcher in the valid-searcher element with more than one customfield-type definition. Similarly, a single custom field type can have more than one searcher. This can be achieved by defining the same custom field type under more than one customfield-searcher module or by using more than one valid-searcher element in the customfield-type module. Note that the valid-searcher module is available only from JIRA 5.2. Once the searcher is defined against the custom field, you can see it appearing in the issue navigator when the correct context is selected. That last part is extremely important because the field will be available to search only when the context chosen is correct. For example, if field X is available only on bugs, it won't appear on the issue navigator when the issue type selected has both bugs and new features. Refresh the search menu after the correct context is selected to see your field. This is applicable only for simple searching. There's more… With the introduction of v2 plugins—courtesy of OSGI bundles—referring to the built-in JIRA searcher classes directly in the atlassian-plugin.xml file will sometimes fail, because it can't resolve all the dependencies (the notorious Unsatisfied dependency… errors). This is because some of the classes are not available for dependency injection in the v2 plugins, as they were in v1 plugins. But there is an easy hack to do it. Just create a dummy custom searcher class with the constructor that does the dependency injection for you: public class MySearcher extends SomeJiraSearcher { public MySearcher(PluginComponent ioc) { super(ioc, ComponentManager.getInstanceOfType(anotherType)); } } Working with Custom Fields 88 If that doesn't work, add the field to the system-customfield-types.xml file under WEB-INF/classes along with the JIRA system custom fields, that is, one more valid- customfield-type entry into the relevant customfield-searcher element. If you do this, remember to apply this workaround when JIRA is upgraded! Dealing with custom fields on an issue In this recipe, we will see how to work with custom fields on an issue. It covers reading a custom field value from an issue and then updating the custom field value on the issue, with and without notifications. Getting ready Identify the places where the custom fields need to be manipulated, be it on a listener, a workflow element, or somewhere else in our plugins. How to do it… We will see how to access the value of a custom field, and modify the value as we go along. Complete the following steps to read the custom field value from an Issue object: 1. Create an instance of the CustomFieldManager class. This is the Manager class that does most of the operations on custom fields. There are two ways to retrieve a Manager class: ‰‰ Inject the Manager class in the constructor of your plugin class implementation. ‰‰ Retrieve the CustomFieldManager directly from the ComponentAccessor class. It can be done as follows: CustomFieldManager customFieldManager = ComponentAccessor. getCustomFieldManager(); 2. Retrieve the customField object using the custom field name: CustomField customField = customFieldManager.getCustomFieldObjectB yName(demoFieldName); Or, retrieve the customField object using the custom field ID: CustomField customField = customFieldManager. getCustomFieldObject(new Long(10000)); Chapter 3 89 3. Once the custom field object is available, its value for an issue can be retrieved as follows: Object value = issue.getCustomFieldValue(customField); 4. Cast the value object to the appropriate class. For example, String for a text field, Option for a select field, List