AtlasCamp 2010: Understanding the Atlassian Platform - Tim Pettersen

Preview:

Citation preview

Understanding the Atlassian Platform

Tim Pettersen, Atlassian

AtlassianPluginDevelopmentPlatform

Targeted at You!FeaturesDedicated TeamDocumentationFocus on Backwards Compatibility

Overview...

Have you ever?

... needed to communicate with a remote application?

... wanted an easy way to provide JSON data for an AJAXy UI?

... wanted to expose your plugin's data via web services?

REST Atlassian

Plugin

Depending on REST ...

<dependency> <groupId>com.atlassian.plugins.rest</groupId> <artifactId>atlassian-rest-common</artifactId> <version>${rest.version}</version> <scope>provided</scope></dependency><dependency> <groupId>com.atlassian.plugins.rest</groupId> <artifactId>atlassian-rest-module</artifactId> <version>${rest.version}</version> <scope>provided</scope></dependency><dependency> <groupId>javax.xml.bind</groupId> <artifactId>jaxb-api</artifactId> <version>2.1</version> <scope>provided</scope></dependency>

Atlassian annotations & utilities

JAX-RS annotations & jersey

JAXB annotations

Quick departure - versioning

Easy - Dependency Management POM

Custom - Specify versions per module

more info: http://bit.ly/platform-versions

Depending on REST ...

<atlassian-plugin>

<rest key="some-key" path="/some-path" version="1.0" description="Initialise REST resources for my plugin" />

<component-import key="restUrlBuilder" interface="com.atlassian.plugins.rest.common.util.RestUrlBuilder" />

</atlassian-plugin>

http://jira.atlassian.com/REST/some-path/1.0/

Using REST ... creating a resource

@Path("/hello/")@Consumes(APPLICATION_XML, APPLICATION_JSON)@Produces(APPLICATION_XML, APPLICATION_JSON)public class MyResource {

@Path("/my-name-is/{name}") @GET @AnonymousAllowed public Response hello(@PathParam("name") String name) { return ok( new MessageEntity("Hello, " + name + "!") ); }}

http://.../REST/some-path/1.0/hello

http://.../REST/some-path/1.0/hello/my-name-is/tim

Using REST ... creating an entity

@XmlAccessorType(FIELD)@XmlRootElement(name = "message")public class MessageEntity { private String body; public MessageEntity(String body) { this.body = body; }}

http://.../REST/some-path/1.0/hello/my-name-is/Tim.xml<message> <body>Hello, Tim!</body></message>

http://.../REST/some-path/1.0/hello/my-name-is/Tim.json{body:"Hello, world!"}

Poster: http://bit.ly/poster-ff

Using REST ... generating a resource URL

public class MyClient {

public void doGet() { String baseUrl = "http://jira.atlassian.com"; String restUrl = restUrlBuilder .getUrlFor(baseUrl, MyResource.class) .hello("Tim") .toString(); ... }}

// generated restUrl = "http://.../REST/some-path/1.0/hello/my-name-is/Tim"

Using REST ... unmarshalling an entity Request request = requestFactory .createRequest(MethodType.GET, restUrl);

request.execute(new ResponseHandler<Response>() { public void handle(Response response) { if (response.isSuccessful()) {

} } });

// unmarshall the entityMessageEntity m = response.getEntity(MessageEntity.class);

Using REST ... marshalling an entity

Request request = requestFactory.createRequest(PUT, restUrl); MessageEntity entity = new MessageEntity("some-value"); request.setEntity(entity); request.execute();

@PUT public Response submit(MessageEntity message) { System.out.println("Received: " + message.getBody()); }

Client

Server

Have you ever?

... wanted to launch a modal dialog from your plugin?

... wanted to add rich tooltips to your content?

... wished someone else would write your CSS and javascript for you?

... wanted your plugin to look and behave a little bit more like the host application?

User Interface Atlassian

Plugin

Depending on AUI ...

public class MyServlet extends HttpServlet { public void doGet() { webResourceManager .requireResource("com.atlassian.auiplugin:ajs"); } }

<html> <head> #webResourceManager.getRequiredResources() </head> <body ... /></html>

Depending on AUI ...

<atlassian-plugin> <web-resource key="some-web-resource"> <resource name="my-script.js" ... /> <resource name="my-styles.css" ... /> <dependency>com.atlassian.auiplugin:ajs</dependency> </web-resource></atlassian-plugin>

public class MyServlet extends HttpServlet { public void doGet() { webResourceManager .requireResource("com.my.plugin:some-web-resource"); } }

Using AUI ... dropdowns

Using AUI ... dropdowns

<script type="text/javascript"> // create drop-down AJS.$("#my-dropdown") .dropDown("standard", {alignment: "right"});</script>

<!-- the list to display as a dropdown --><ul id="my-dropdown"> <li class="paper-clip">Item One</li> <li class="clock">Item Two</li> <li class="envelope">Item Three</li> </ul>

Using AUI ... tooltips

Using AUI ... tooltips

<script type="text/javascript"> // assign URL for retrieving AJAX content var contentUrl = AJS.params.baseUrl + "/rest/myplugin/1.0/tooltip"; // bind hovers to DOM elements AJS.InlineDialog(".tooltip-link", "tooltip", contentUrl, { onHover: true, width: 300, cacheContent: true });</script>

<!-- an anchor that the tooltip will be applied to --><a class='.hover-link'>Hover over me!</a>

Using AUI ... more!

Have you ever?

... wanted to include a piece of javascript or CSS on every page in Confluence?

... wanted to make your plugin, pluggable?

... wanted to build a single plugin that can be deployed in multiple applications?

Plugins Framework Atlassian

Depending on plugins ...

<dependency> <groupId>com.atlassian.plugins</groupId> <artifactId>atlassian-plugins-webresource</artifactId> <version>${atlassian.plugins.version}</version> <scope>provided</scope></dependency>

<dependency> <groupId>com.atlassian.plugins</groupId> <artifactId>atlassian-plugins-core</artifactId> <version>${atlassian.plugins.version}</version> <scope>provided</scope></dependency>

<atlassian-plugin> <!-- Unnecessary! --> <component-import ... /></atlassian-plugin>

Using plugins ... web-resource contexts

<atlassian-plugin> <web-resource key="some-web-resource"> <context>atl.general</context> <resource name="my-script.js" ... /> <resource name="my-styles.css" ... /> </web-resource></atlassian-plugin>

atl.general

atl.admin

atl.userprofile

Custom Plugin Modules

Using plugins ... custom module types

public class MyDescriptor extends AbstractModuleDescriptor<MyModule> {

public void init(Plugin plugin, Element element);

public MyModule getModule() { // initialize the module specified by the descriptor's class attribute return moduleFactory.createModule(moduleClassName, this); }

}<atlassian-plugin> <module-type key="my-descriptor" class="com.myplugin.MyDescriptor" /> <my-descriptor class="com.myplugin.ModuleOne" /> <my-descriptor class="com.myplugin.ModuleTwo" />

</atlassian-plugin>

Using plugins ... custom module types

public class MyService {

private PluginAccessor pluginAccessor;

public MyService(PluginAccessor pluginAccessor) { this.pluginAccessor = pluginAccessor; } public void executeInstalledModules() { for (MyModule module : pluginAccessor.getEnabledModulesByClass(MyModule.class)) { module.execute(); } }

}

Using plugins ... descriptor application scopes<atlassian-plugin>

<web-resource key="some-web-resource" application="confluence"> <context>atl.general</context> <resource name="page-integration.js" ... /> </web-resource> <web-resource key="some-web-resource" application="jira"> <context>atl.general</context> <resource name="issue-integration.js" ... /> </web-resource>

<!-- depends on Confluence's API --> <component key="some-component" application="confluence" class="com.myplugin.PageHandler" /> <!-- depends on JIRA's API --> <component key="some-component" application="jira" class="com.myplugin.IssueHandler" /> </atlassian-plugin>

Have you ever?

... needed to render HTML outside of a JIRA or Confluence action?

... needed to pass back rendered HTML to use in an AJAX UI?

... wished you could use something a little more modern than Velocity 1.4 in JIRA?

Template Renderer Atlassian

Depending on ATR ...

<dependency> <groupId>com.atlassian.templaterenderer</groupId> <artifactId>atlassian-template-renderer-api</artifactId> <version>${template.renderer.version}</version> <scope>provided</scope></dependency>

<atlassian-plugin> <component-import key="templateRenderer" interface="com.atlassian.templaterenderer.TemplateRenderer" /></atlassian-plugin>

Using ATR ...

public class MyServlet extends HttpServlet {

private TemplateRenderer templateRenderer; // constructor-injected

public void doGet(HttpServletRequest req, HttpServletResponse resp) { Map<String, Object> context = createContext(); templateRenderer.render("templates/view.vm", context, resp.getWriter() ); } }

Have you ever?

... needed to persist some simple data for your plugin?

... wanted to i18n your plugin?

... needed to write an upgrade task?

... wanted to schedule a recurring job?

Shared Application Layer Atlassian

Plugin

Depending on SAL ...

<dependency> <groupId>com.atlassian.sal</groupId> <artifactId>sal-api</artifactId> <version>${sal.version}</version> <scope>provided</scope></dependency>

Depending on SAL ...

<atlassian-plugin>

<component-import key="[someSalComponent]" interface="com.atlassian.sal.api.[someSalComponent]" />

</atlassian-plugin>

AuthenticationControllerLoginUriProviderComponentLocatorI18nResolverLocaleResolverRequestFactorySearchProvider

PluginUpgradeManagerUserManagerApplicationPropertiesPluginSettingsFactoryProjectManagerPluginSchedulerMore!

Using SAL ... upgrade tasks

public interface PluginUpgradeTask { int getNumber(); String getShortDescription(); Collection<Message> doUpgrade(); String getPluginKey();}

<atlassian-plugin> <component key="my-upgrade-task" public="true" class="com.myplugin.MyUpgradeTask"> <interface>com.atlassian.sal.api.upgrade.PluginUpgradeTask</interface> </component></atlassian-plugin>

Using SAL ... i18n

<atlassian-plugin>

<component-import key="i18nResolver" interface="com.atlassian.sal.api.message.I18nResolver" />

<resource key="my-i18n" type="i18n" location="i18n" />

</atlassian-plugin>

hello.world = Hello World! (i18n.properties)

hello.world: Hola a todos! (i18n_es.properties)

hello.world Hallo Welt! (i18n_de.properties)

Using SAL ... i18n

Map<String, Object> context = new HashMap<String, Object>();context.put("i18n", i18nResolver);templateRenderer.render("templates/view.vm", context, writer);

<html> <head> <title>#i18n.getText('hello.world')</title> </head> <body ... /></html>

Using SAL ... job scheduling

// simple very-contrived jobpublic class MyPluginJob implements PluginJob {

public void execute(Map<String, Object> data) { int count = data.get("jobExecutionCount"); count++; data.put("jobExecutionCount", count); }

}

Using SAL ... job scheduling

public class MyComponent implements LifecycleAware { private PluginScheduler scheduler; // autowired

public void onStart() { scheduler.scheduleJob( "my-plugin-job", MyPluginJob.class, new HashMap<String, Object>(), new Date(), 10000 // milliseconds );

}}

Targeted at You!FeaturesDedicated TeamDocumentationBackwards Compatibility

Open Source :D

Go use it!

Shared Application Layer

Unified Application Links Template Renderer

Plugins Framework

Atlassian User Interface REST

Questions?

more info - http://bit.ly/best-thing-ever