connecting connect with spring boot

109
VINCENT KOK DEVELOPMENT MANAGER ATLASSIAN @VINCENTKOK Connecting Connect with Spring Boot

Upload: vincent-kok

Post on 14-Jan-2017

379 views

Category:

Technology


0 download

TRANSCRIPT

VINCENT KOK • DEVELOPMENT MANAGER • ATLASSIAN • @VINCENTKOK

Connecting Connect with Spring Boot

Atlassian Supported

Community Frameworks

go.atlassian.com/connectframeworks

S TA R T E R S

S P R I N G B O O T

C O N N E C T S TA R T E R

I N C E P T I O N

Agenda

S P R I N G B O O T

http://geek-and-poke.com/geekandpoke/2014/1/2/games-for-the-real-geeks-part-2

A “vanilla” Spring web app

A “vanilla” Spring web app

1. Dependencies Setup the right Maven dependencies

A “vanilla” Spring web app

1. Dependencies Setup the right Maven dependencies

web.xml Setup your web.xml

A “vanilla” Spring web app

1. Dependencies Setup the right Maven dependencies

application-context.xml Enable component scanning and mvc annotation driven

web.xml Setup your web.xml

A “vanilla” Spring web app

1. Dependencies Setup the right Maven dependencies

4. The actual controller What you actually care about

application-context.xml Enable component scanning and mvc annotation driven

web.xml Setup your web.xml

A “vanilla” Spring web app

5. Install Tomcat And configure it as well

1. Dependencies Setup the right Maven dependencies

4. The actual controller What you actually care about

application-context.xml Enable component scanning and mvc annotation driven

web.xml Setup your web.xml

A “vanilla” Spring web app

5. Install Tomcat And configure it as well

6. It might work You probably made a typo

1. Dependencies Setup the right Maven dependencies

4. The actual controller What you actually care about

application-context.xml Enable component scanning and mvc annotation driven

web.xml Setup your web.xml

• Big cool statistic

• 2,569

• Add-Ons in Marketplace

Repeatable pattern

https://flic.kr/p/8ykpkW

Spring BootRepeatable patterns for services

Fast Opinionated Auto Configured No code generation

Example: Hello world

e

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>

<groupId>com.atlassian.connect.lighthing</groupId> <artifactId>lightingdemo</artifactId> <version>1.0-SNAPSHOT</version>

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.1.RELEASE</version> </parent>

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency>

Example: Hello world

e

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>

<groupId>com.atlassian.connect.lighthing</groupId> <artifactId>lightingdemo</artifactId> <version>1.0-SNAPSHOT</version>

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.1.RELEASE</version> </parent>

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency>

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.1.RELEASE</version> </parent>

Example: Hello world

e

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0

http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion>

<groupId>com.atlassian.connect.lighthing</groupId> <artifactId>lightingdemo</artifactId> <version>1.0-SNAPSHOT</version>

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.1.RELEASE</version> </parent>

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency>

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency>

Example: Hello world

e

package hello;

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;

@SpringBootApplicationpublic class HelloApplication {

public static void main(String[] args) { SpringApplication.run(HelloApplication.class, args); }}

Example: Hello world

e

package hello;

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;

@SpringBootApplicationpublic class HelloApplication {

public static void main(String[] args) { SpringApplication.run(HelloApplication.class, args); }}

@SpringBootApplicationpublic class HelloApplication {

Example: Hello world

e

package hello;

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.context.ApplicationContext;

@SpringBootApplicationpublic class HelloApplication {

public static void main(String[] args) { SpringApplication.run(HelloApplication.class, args); }}

public static void main(String[] args) {SpringApplication.run(HelloApplication.class, args);

}

Example: Hello world

e

package hello;

import org.springframework.web.bind.annotation.RestController;import org.springframework.web.bind.annotation.RequestMapping;

@RestControllerpublic class HelloController {

@RequestMapping("/") public String index() { return "Greetings from Spring Boot!"; }}

Example: Hello world

e

package hello;

import org.springframework.web.bind.annotation.RestController;import org.springframework.web.bind.annotation.RequestMapping;

@RestControllerpublic class HelloController {

@RequestMapping("/") public String index() { return "Greetings from Spring Boot!"; }}

@RestControllerpublic class HelloController {

Example: Hello world

e

package hello;

import org.springframework.web.bind.annotation.RestController;import org.springframework.web.bind.annotation.RequestMapping;

@RestControllerpublic class HelloController {

@RequestMapping("/") public String index() { return "Greetings from Spring Boot!"; }}

@RequestMapping("/") public String index() { return "Greetings from Spring Boot!"; }

Example: Hello world

e

$> mvn spring-boot:run

Example: Hello world

e

$> mvn spring-boot:run

$> curl http://localhost:8080/

Greetings from Spring Boot!

Provides production ready features Think health checks, metrics, security and externalised configuration

S TA R T E R S

S P R I N G B O O T

C O N N E C T S TA R T E R

I N C E P T I O N

Agenda

S TA R T E R S

Spring Boot Starters

Building blocks Out of the box Build your own

Building blocks for your service

Example: Consume a starter

e

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependencies>

Example: Consume a starter

e

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependencies>

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency>

Example: Consume a starter

e

$> mvn spring-boot:run

Example: Consume a starter

e

$> mvn spring-boot:run

$> curl http://localhost:8080/health

{”status”:”UP”}

Available Starters

Out of the box

63** as counted by myself at midnight

S TA R T E R S

S P R I N G B O O T

C O N N E C T S TA R T E R

I N C E P T I O N

Agenda

C O N N E C T S TA R T E R

Three things every add-on requires

Lifecycle Descriptor JWT

1: /atlassian-connect.json

1: /atlassian-connect.json

2: Descriptor JSON

1: /atlassian-connect.json

2: Descriptor JSON

3: /install

1: /atlassian-connect.json

2: Descriptor JSON

3: /install

4. Store tenant data

1: /atlassian-connect.json

2: Descriptor JSON

3: /install

5: Install OK / not OK

4. Store tenant data

Descriptor

e

{ "name": "Inception", "description": “A page in a page", "key": “com.atlassian.connect.atlascamp.inception”, "baseUrl": “http://localhost:8080”, "vendor": { "name": "Atlassian", "url": "http://www.atlassian.com" }, "authentication": { "type": "jwt" }, "lifecycle": { "installed": "/installed", "uninstalled": "/uninstalled", "enabled": "/enabled", "disabled": "/disabled" }, "apiVersion": 1, "scopes": [ "read" ], "modules": { }}

Descriptor

e

{ "name": "Inception", "description": “A page in a page", "key": “com.atlassian.connect.atlascamp.inception”, "baseUrl": “http://localhost:8080”, "vendor": { "name": "Atlassian", "url": "http://www.atlassian.com" }, "authentication": { "type": "jwt" }, "lifecycle": { "installed": "/installed", "uninstalled": "/uninstalled", "enabled": "/enabled", "disabled": "/disabled" }, "apiVersion": 1, "scopes": [ "read" ], "modules": { }}

"name": "Inception", "description": “A page in a page", "key": “com.atlassian.connect.atlascamp.inception”, "baseUrl": “http://localhost:8080”, "vendor": { "name": "Atlassian", "url": "http://www.atlassian.com" },

Descriptor

e

{ "name": "Inception", "description": “A page in a page", "key": “com.atlassian.connect.atlascamp.inception”, "baseUrl": “http://localhost:8080”, "vendor": { "name": "Atlassian", "url": "http://www.atlassian.com" }, "authentication": { "type": "jwt" }, "lifecycle": { "installed": "/installed", "uninstalled": "/uninstalled", "enabled": "/enabled", "disabled": "/disabled" }, "apiVersion": 1, "scopes": [ "read" ], "modules": { }}

"lifecycle": { "installed": "/installed", "uninstalled": "/uninstalled", "enabled": "/enabled", "disabled": "/disabled" }

Descriptor

e

{ "name": "Inception", "description": “A page in a page", "key": “com.atlassian.connect.atlascamp.inception”, "baseUrl": “http://localhost:8080”, "vendor": { "name": "Atlassian", "url": "http://www.atlassian.com" }, "authentication": { "type": "jwt" }, "lifecycle": { "installed": "/installed", "uninstalled": "/uninstalled", "enabled": "/enabled", "disabled": "/disabled" }, "apiVersion": 1, "scopes": [ "read" ], "modules": { }}

"modules": {}

Lifecycle

e

{ "key": “com.atlassian.connect.atlascamp.inception”, "clientKey": "Confluence:5358671948", "publicKey": "MIGf....ZRWzwIDAQAB", "sharedSecret": "fda9e230-ba1b-4e8d-a76e-c9e4e6c28ea8", "serverVersion": "6437", "pluginsVersion": "1.1.81", "baseUrl": “http://vincentkok.atlassian.net", "productType": "confluence", "description": "Atlassian Confluene at https://example.atlassian.net", "eventType": "INSTALLED"}

Lifecycle

e

{ "key": “com.atlassian.connect.atlascamp.inception”, "clientKey": "Confluence:5358671948", "publicKey": "MIGf....ZRWzwIDAQAB", "sharedSecret": "fda9e230-ba1b-4e8d-a76e-c9e4e6c28ea8", "serverVersion": "6437", "pluginsVersion": "1.1.81", "baseUrl": “http://vincentkok.atlassian.net", "productType": "confluence", "description": "Atlassian Confluene at https://example.atlassian.net", "eventType": "INSTALLED"}

"key": “com.atlassian.connect.atlascamp.inception”, "clientKey": "Confluence:5358671948",

Lifecycle

e

{ "key": “com.atlassian.connect.atlascamp.inception”, "clientKey": "Confluence:5358671948", "publicKey": "MIGf....ZRWzwIDAQAB", "sharedSecret": "fda9e230-ba1b-4e8d-a76e-c9e4e6c28ea8", "serverVersion": "6437", "pluginsVersion": "1.1.81", "baseUrl": “http://vincentkok.atlassian.net", "productType": "confluence", "description": "Atlassian Confluene at https://example.atlassian.net", "eventType": "INSTALLED"}

"sharedSecret": "fda9e230-ba1b-4e8d-a76e-c9e4e6c28ea8",

Lifecycle

e

{ "key": “com.atlassian.connect.atlascamp.inception”, "clientKey": "Confluence:5358671948", "publicKey": "MIGf....ZRWzwIDAQAB", "sharedSecret": "fda9e230-ba1b-4e8d-a76e-c9e4e6c28ea8", "serverVersion": "6437", "pluginsVersion": "1.1.81", "baseUrl": “http://vincentkok.atlassian.net", "productType": "confluence", "description": "Atlassian Confluene at https://example.atlassian.net", "eventType": "INSTALLED"} “eventType": "INSTALLED",

JWT

e

<base64url-encoded header>.

<base64url-encoded claims>.

<base64url-encoded signature>

JWT

e

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjEzODY4OTkxMzEsImlzcyI6ImppcmE6MTU0ODk1OTUiLCJxc2giOiI4MDYzZmY0Y2ExZTQxZGY3YmM5MGM4YWI2ZDBmNjIwN2Q0OTFjZjZkYWQ3YzY2ZWE3OTdiNDYxNGI3MTkyMmU5IiwiaWF0IjoxMzg2ODk4OTUxfQ.uKqU9dTB6gKwG6jQCuXYAiMNdfNRw98Hw_IWuA5MaMo

JWT

e

{ "typ":"JWT", "alg":"HS256"}

{ "iss": "confluence:1314039", "iat": 1300819370, "exp": 1300819380, "qsh": "8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9", "sub": "alana", "context": { "user": { "userKey": "alana", "username": "aaware", "displayName": "Bruce Wayne" } }}

JWT

e

{ "typ":"JWT", "alg":"HS256"}

{ "iss": "confluence:1314039", "iat": 1300819370, "exp": 1300819380, "qsh": "8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9", "sub": "alana", "context": { "user": { "userKey": "alana", "username": "aaware", "displayName": "Bruce Wayne" } }}

{ "typ":"JWT", "alg":"HS256"}

JWT

e

{ "typ":"JWT", "alg":"HS256"}

{ "iss": "confluence:1314039", "iat": 1300819370, "exp": 1300819380, "qsh": "8063ff4ca1e41df7bc90c8ab6d0f6207d491cf6dad7c66ea797b4614b71922e9", "sub": "alana", "context": { "user": { "userKey": "alana", "username": "aaware", "displayName": "Bruce Wayne" } }}

"iss": "confluence:1314039", "iat": 1300819370, "exp": 1300819380, "qsh": “8063ff4…2e9”, "sub": "alana",

Authenticated requests

e

/inception-macro?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJl…

Authenticated requests

e"Authorization" : "JWT eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJl..."

/inception-macro?jwt=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJl…

• Big cool statistic

• 2,569

• Add-Ons in Marketplace

DRY

https://flic.kr/p/qkUpu

Atlassian Supported

Community Frameworks

Atlassian Supported

Community Frameworks

BETA

http://go.atlassian.com/ac-springboot

S TA R T E R S

S P R I N G B O O T

C O N N E C T S TA R T E R

I N U N D E R 3 M I N U T E S

Agenda

I N C E P T I O N

• Big cool statistic

• 2,569

• Add-Ons in Marketplace

Let’s build a Connect add-on from scratch

https://flic.kr/p/rtmnT2

• Big cool statistic

• 2,569

• Add-Ons in Marketplace

A page in a page

https://flic.kr/p/216z4i

1: inception-macro?pageId=5&jwt=eyJhbGciOiJIUz

1: inception-macro?pageId=5&jwt=eyJhbGciOiJIUz

2:/rest/api/content/5?expand=body.export_view

1: inception-macro?pageId=5&jwt=eyJhbGciOiJIUz

2:/rest/api/content/5?expand=body.export_view

3: Page content as JSON

1: inception-macro?pageId=5&jwt=eyJhbGciOiJIUz

2:/rest/api/content/5?expand=body.export_view

3: Page content as JSON

4: Rendered macro HTML

Create a project

e

$> mvn archetype:generate -DgroupId=com.atlassian.labs.atlascamp -DartifactId=inception -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Create a project

e

$ ls inception

pom.xmlsrc

Add dependencies

e

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion> <groupId>com.atlassian.labs.atlascamp</groupId> <artifactId>inception</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version>

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.RELEASE</version> </parent>

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies></project>

Add dependencies

e

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion> <groupId>com.atlassian.labs.atlascamp</groupId> <artifactId>inception</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version>

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.RELEASE</version> </parent>

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies></project>

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.1.RELEASE</version> </parent>

Add dependencies

e

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">

<modelVersion>4.0.0</modelVersion> <groupId>com.atlassian.labs.atlascamp</groupId> <artifactId>inception</artifactId> <packaging>jar</packaging> <version>1.0-SNAPSHOT</version>

<parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>1.3.3.RELEASE</version> </parent>

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> </dependencies></project>

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.atlassian.connect</groupId> <artifactId>atlassian—spring-boot-starter</artifactId> <version>1.0.0-beta-1</version> </dependency></dependencies>

Create the application

e

package com.atlassian.connect.atlascamp;

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplicationpublic class InceptionApplication { public static void main( String[] args ) { SpringApplication.run(InceptionApplication.class, args); }}

Create the application

e

package com.atlassian.connect.atlascamp;

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplicationpublic class InceptionApplication { public static void main( String[] args ) { SpringApplication.run(InceptionApplication.class, args); }}

@SpringBootApplicationpublic class InceptionApplication {

Create the application

e

package com.atlassian.connect.atlascamp;

import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplicationpublic class InceptionApplication { public static void main( String[] args ) { SpringApplication.run(InceptionApplication.class, args); }}public static void main(String[] args) {SpringApplication.run(InceptionApplication.class, args);

}

Descriptor

e

$> touch src/main/resources/atlassian-connect.json

Descriptor

e

{ "name": "Inception", "description": “A page in a page", "key": “com.atlassian.connect.atlascamp.inception”, "baseUrl": “http://localhost:8080”, "vendor": { "name": "Atlassian", "url": "http://www.atlassian.com" }, "authentication": { "type": "jwt" }, "lifecycle": { "installed": "/installed", "uninstalled": "/uninstalled", "enabled": "/enabled", "disabled": "/disabled" }, "apiVersion": 1, "scopes": [ "read" ], "modules": { }}

Descriptor

e

{ "name": "Inception", "description": “A page in a page", "key": “com.atlassian.connect.atlascamp.inception”, "baseUrl": “http://localhost:8080”, "vendor": { "name": "Atlassian", "url": "http://www.atlassian.com" }, "authentication": { "type": "jwt" }, "lifecycle": { "installed": "/installed", "uninstalled": "/uninstalled", "enabled": "/enabled", "disabled": "/disabled" }, "apiVersion": 1, "scopes": [ "read" ], "modules": { }}

"name": "Inception", "description": “A page in a page", "key": “com.atlassian.connect.atlascamp.inception”, "baseUrl": “https://1c12a2cd.ngrok.io”, "vendor": { "name": "Atlassian", "url": "http://www.atlassian.com" },

Descriptor

e

{ "name": "Inception", "description": “A page in a page", "key": “com.atlassian.connect.atlascamp.inception”, "baseUrl": “http://localhost:8080”, "vendor": { "name": "Atlassian", "url": "http://www.atlassian.com" }, "authentication": { "type": "jwt" }, "lifecycle": { "installed": "/installed", "uninstalled": "/uninstalled", "enabled": "/enabled", "disabled": "/disabled" }, "apiVersion": 1, "scopes": [ "read" ], "modules": { }}

"modules": {}

Descriptor

e

"modules": { "dynamicContentMacros": [ { "url": "/inception-macro?pageId={page.id}", "description": { "value": "A page in a page" }, "name": { "value": "Inception" }, "categories": [ "visuals" ], "outputType": "block", "bodyType": "none", "aliases": [ "inception" ], "featured": true, "parameters": [], "key": "inception-macro" } ] }

Descriptor

e

"modules": { "dynamicContentMacros": [ { "url": "/inception-macro?pageId={page.id}", "description": { "value": "A page in a page" }, "name": { "value": "Inception" }, "categories": [ "visuals" ], "outputType": "block", "bodyType": "none", "aliases": [ "inception" ], "featured": true, "parameters": [], "key": "inception-macro" } ] }

"dynamicContentMacros": [ { "url": "/inception-macro?pageId={page.id}", "description": { "value": "A page in a page" },

Tenant storage

e

HHH000227: Running hbm2ddl schema export

Tenant storage

e

spring.jpa.database=POSTGRESQL

spring.jpa.hibernate.ddl-auto=none

spring.datasource.url=jdbc:postgresql://localhost:5432/springbootdb

Macro template

e

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.atlassian.connect</groupId> <artifactId>atlassian-connect-spring-boot-starter</artifactId> <version>0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies>

Macro template

e

<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.atlassian.connect</groupId> <artifactId>atlassian-connect-spring-boot-starter</artifactId> <version>0.1-SNAPSHOT</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency> </dependencies>

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> </dependency>

Macro template

e

$> touch src/main/resources/templates/inception-macro.html

Macro template

e

<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <link rel="stylesheet" href="//aui-cdn.atlassian.com/aui.min.css" media="all"/> <script th:src=“${js-all-url}” type="text/javascript"></script></head><body> <section id="content" class="ac-content"> <p th:utext="${body}"/> </section></body></html>

Macro template

e

<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <link rel="stylesheet" href="//aui-cdn.atlassian.com/aui.min.css" media="all"/> <script th:src=“${js-all-url}” type="text/javascript"></script></head><body> <section id="content" class="ac-content"> <p th:utext="${body}"/> </section></body></html>

<script th:src=“${js-all-url}” type="text/javascript"></script>

Macro template

e

<!DOCTYPE html><html lang="en" xmlns:th="http://www.thymeleaf.org"><head> <link rel="stylesheet" href="//aui-cdn.atlassian.com/aui.min.css" media="all"/> <script th:src=“${js-all-url}” type="text/javascript"></script></head><body> <section id="content" class="ac-content"> <p th:utext="${body}"/> </section></body></html>

<section id="content" class="ac-content"> <p th:utext="${body}"/> </section>

Controller

e

package com.atlassian.connect.atlascamp;

@Controllerpublic class InceptionController {

private static final String PAGE_URL = "%s/rest/api/content/%s?expand=body.export_view"; private static final String JS_URL = "%s/atlassian-connect/all.js";

@Autowired private RestTemplate restTemplate;

@RequestMapping(value = "/inception-macro") public String render(@RequestParam long pageId,

@AuthenticationPrincipal AtlassianHostUser hostUser, Model model) { final String baseURL = hostUser.getHost().getBaseUrl(); final String contentUrl = String.format(PAGE_URL, baseURL, pageId); final String pageContent = restTemplate.getForObject(contentUrl, String.class); final String pageBody = extractPageBody(pageContent);

model.addAttribute("body", pageBody); model.addAttribute("jsurl", String.format(JS_URL, baseURL));

return "inception-macro"; }

private String extractPageBody(String response) {

Controller

e

package com.atlassian.connect.atlascamp;

@Controllerpublic class InceptionController {

private static final String PAGE_URL = "%s/rest/api/content/%s?expand=body.export_view"; private static final String JS_URL = "%s/atlassian-connect/all.js";

@Autowired private RestTemplate restTemplate;

@RequestMapping(value = "/inception-macro") public String render(@RequestParam long pageId,

@AuthenticationPrincipal AtlassianHostUser hostUser, Model model) { final String baseURL = hostUser.getHost().getBaseUrl(); final String contentUrl = String.format(PAGE_URL, baseURL, pageId); final String pageContent = restTemplate.getForObject(contentUrl, String.class); final String pageBody = extractPageBody(pageContent);

model.addAttribute("body", pageBody); model.addAttribute("jsurl", String.format(JS_URL, baseURL));

return "inception-macro"; }

private String extractPageBody(String response) {

@Controllerpublic class InceptionController {

Controller

e

package com.atlassian.connect.atlascamp;

@Controllerpublic class InceptionController {

private static final String PAGE_URL = "%s/rest/api/content/%s?expand=body.export_view"; private static final String JS_URL = "%s/atlassian-connect/all.js";

@Autowired private RestTemplate restTemplate;

@RequestMapping(value = "/inception-macro") public String render(@RequestParam long pageId,

@AuthenticationPrincipal AtlassianHostUser hostUser, Model model) { final String baseURL = hostUser.getHost().getBaseUrl(); final String contentUrl = String.format(PAGE_URL, baseURL, pageId); final String pageContent = restTemplate.getForObject(contentUrl, String.class); final String pageBody = extractPageBody(pageContent);

model.addAttribute("body", pageBody); model.addAttribute("jsurl", String.format(JS_URL, baseURL));

return "inception-macro"; }

private String extractPageBody(String response) {

private static final String PAGE_URL private static final String JS_URL

Controller

e

package com.atlassian.connect.atlascamp;

@Controllerpublic class InceptionController {

private static final String PAGE_URL = "%s/rest/api/content/%s?expand=body.export_view"; private static final String JS_URL = "%s/atlassian-connect/all.js";

@Autowired private RestTemplate restTemplate;

@RequestMapping(value = "/inception-macro") public String render(@RequestParam long pageId,

@AuthenticationPrincipal AtlassianHostUser hostUser, Model model) { final String baseURL = hostUser.getHost().getBaseUrl(); final String contentUrl = String.format(PAGE_URL, baseURL, pageId); final String pageContent = restTemplate.getForObject(contentUrl, String.class); final String pageBody = extractPageBody(pageContent);

model.addAttribute("body", pageBody); model.addAttribute("jsurl", String.format(JS_URL, baseURL));

return "inception-macro"; }

private String extractPageBody(String response) {

@Autowired private RestTemplate restTemplate;

Controller

e

package com.atlassian.connect.atlascamp;

@Controllerpublic class InceptionController {

private static final String PAGE_URL = "%s/rest/api/content/%s?expand=body.export_view"; private static final String JS_URL = "%s/atlassian-connect/all.js";

@Autowired private RestTemplate restTemplate;

@RequestMapping(value = "/inception-macro") public String render(@RequestParam long pageId,

@AuthenticationPrincipal AtlassianHostUser hostUser, Model model) { final String baseURL = hostUser.getHost().getBaseUrl(); final String contentUrl = String.format(PAGE_URL, baseURL, pageId); final String pageContent = restTemplate.getForObject(contentUrl, String.class); final String pageBody = extractPageBody(pageContent);

model.addAttribute("body", pageBody); model.addAttribute("jsurl", String.format(JS_URL, baseURL));

return "inception-macro"; }

private String extractPageBody(String response) {

@RequestMapping(value = "/inception-macro") public String render(@RequestParam long pageId,

Controller

e

package com.atlassian.connect.atlascamp;

@Controllerpublic class InceptionController {

private static final String PAGE_URL = "%s/rest/api/content/%s?expand=body.export_view"; private static final String JS_URL = "%s/atlassian-connect/all.js";

@Autowired private RestTemplate restTemplate;

@RequestMapping(value = "/inception-macro") public String render(@RequestParam long pageId,

@AuthenticationPrincipal AtlassianHostUser hostUser, Model model) { final String baseURL = hostUser.getHost().getBaseUrl(); final String contentUrl = String.format(PAGE_URL, baseURL, pageId); final String pageContent = restTemplate.getForObject(contentUrl, String.class); final String pageBody = extractPageBody(pageContent);

model.addAttribute("body", pageBody); model.addAttribute("jsurl", String.format(JS_URL, baseURL));

return "inception-macro"; }

private String extractPageBody(String response) {

@AuthenticationPrincipal AtlassianHostUser hostUser, Model model) { final String baseURL = hostUser.getHost().getBaseUrl();

Controller

e

package com.atlassian.connect.atlascamp;

@Controllerpublic class InceptionController {

private static final String PAGE_URL = "%s/rest/api/content/%s?expand=body.export_view"; private static final String JS_URL = "%s/atlassian-connect/all.js";

@Autowired private RestTemplate restTemplate;

@RequestMapping(value = "/inception-macro") public String render(@RequestParam long pageId,

@AuthenticationPrincipal AtlassianHostUser hostUser, Model model) { final String baseURL = hostUser.getHost().getBaseUrl(); final String contentUrl = String.format(PAGE_URL, baseURL, pageId); final String pageContent = restTemplate.getForObject(contentUrl, String.class); final String pageBody = extractPageBody(pageContent);

model.addAttribute("body", pageBody); model.addAttribute("jsurl", String.format(JS_URL, baseURL));

return "inception-macro"; }

private String extractPageBody(String response) {

final String pageContent = restTemplate.getForObject(contentUrl, String.class); final String pageBody = extractPageBody(pageContent);

Controller

e

package com.atlassian.connect.atlascamp;

@Controllerpublic class InceptionController {

private static final String PAGE_URL = "%s/rest/api/content/%s?expand=body.export_view"; private static final String JS_URL = "%s/atlassian-connect/all.js";

@Autowired private RestTemplate restTemplate;

@RequestMapping(value = "/inception-macro") public String render(@RequestParam long pageId,

@AuthenticationPrincipal AtlassianHostUser hostUser, Model model) { final String baseURL = hostUser.getHost().getBaseUrl(); final String contentUrl = String.format(PAGE_URL, baseURL, pageId); final String pageContent = restTemplate.getForObject(contentUrl, String.class); final String pageBody = extractPageBody(pageContent);

model.addAttribute("body", pageBody); model.addAttribute("jsurl", String.format(JS_URL, baseURL));

return "inception-macro"; }

private String extractPageBody(String response) {

model.addAttribute("body", pageBody); model.addAttribute("jsurl", String.format(JS_URL, baseURL));

return "inception-macro";

Secure

e

$> mvn spring-boot:run

Secure

e

$> mvn spring-boot:run

$> curl http://localhost:8080/inception-macro

401

Install

e

$> npm install -g ngrok

$> ngrok http 8080

Forwarding Forwarding https://1c12a2cd.ngrok.io -> localhost:8080

Install

$> mvn spring-boot:run

$> curl http://localhost:8080/inception-macro

401

Install

$> mvn spring-boot:run

$> curl http://localhost:8080/inception-macro

401

Inception

Inception

Executed requests

e

HTTP Requests

-------------

GET /inception-macro 200 OK

POST /installed 204 No Content

GET /atlassian-connect.json 200 OK

http://go.atlassian.com/inception

Connecting Atlassian Connect with Spring BootWhy again?

Leverage skills Fast DRY Care free Connect

VINCENT KOK • DEVELOPMENT MANAGER • ATLASSIAN • @VINCENTKOK

Thank you!