SlideShare a Scribd company logo
Mum, I want to be a Groovy
full-stack developer
@ilopmarIván López
Hello!
I am Iván López
@ilopmar
http://greachconf.com@madridgug
Thank you very much!
Q&A
Just kidding!
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
Full-stack developer
Backend language
Javascript
HTML
Mobile App
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
Polaromatic
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
1.
Demo
2.
Application flow
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
3.
Backend
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
Polaromatic
▷ Spring Boot
▷ Core App
▷ Spring MVC
▷ Spring Integration Flow
<file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/>
<chain input-channel="incommingFilesChannel">
<service-activator ref="fileService" method="preprocessFile"/>
<service-activator ref="imageConverterService" method="applyEffect"/>
<service-activator ref="browserPushService" method="pushToBrowser"/>
<service-activator ref="metricsService" method="updateMetrics"/>
<service-activator ref="fileService" method="deleteTempFiles"/>
</chain>
Spring Integration Flow
<file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/>
<chain input-channel="incommingFilesChannel">
<service-activator ref="fileService" method="preprocessFile"/>
<service-activator ref="imageConverterService" method="applyEffect"/>
<service-activator ref="browserPushService" method="pushToBrowser"/>
<service-activator ref="metricsService" method="updateMetrics"/>
<service-activator ref="fileService" method="deleteTempFiles"/>
</chain>
Spring Integration Flow
Photo preprocessFile(File file) {
def pr = new PolaroidRequest(file)
this.preprocessFile(pr)
}
File service
<file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/>
<chain input-channel="incommingFilesChannel">
<service-activator ref="fileService" method="preprocessFile"/>
<service-activator ref="imageConverterService" method="applyEffect"/>
<service-activator ref="browserPushService" method="pushToBrowser"/>
<service-activator ref="metricsService" method="updateMetrics"/>
<service-activator ref="fileService" method="deleteTempFiles"/>
</chain>
Spring Integration Flow
File service
Photo preprocessFile(File file) {
def pr = new PolaroidRequest(file)
this.preprocessFile(pr)
} Photo preprocessFile(PolaroidRequest polaroidRequest) {
String outputFile = File.createTempFile("output", ".png").path
return new Photo(input: polaroidRequest.inputFile.absolutePath,
output: outputFile, text: polaroidRequest.text)
}
class ImageConverterService {
private static final String DEFAULT_CAPTION = "#LearningSpringBoot with Polaromaticn"
Random rnd = new Random()
Photo applyEffect(Photo photo) {
log.debug "Applying effect to file: ${photo.input}..."
def inputFile = photo.input
def outputFile = photo.output
double polaroidRotation = rnd.nextInt(6).toDouble()
String caption = photo.text ?: DEFAULT_CAPTION
def op = new IMOperation()
op.addImage(inputFile)
op.thumbnail(300, 300)
.set("caption", caption)
.gravity("center")
.pointsize(20)
.background("black")
.polaroid(rnd.nextBoolean() ? polaroidRotation : -polaroidRotation)
.addImage(outputFile)
def command = new ConvertCmd()
command.run(op)
photo
}
}
Image converter
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
FlickrDownloader
▷ Spring Boot CLI
▷ Download Flickr Interesting pictures
▷ Jsoup, GPars
▷ 55 lines of Groovy code
(microservice?)
@Slf4j
@EnableScheduling
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('commons-io:commons-io:2.4')
@Grab('org.codehaus.gpars:gpars:1.2.1')
class FlickrDownloader {
static final String FLICKER_INTERESTING_URL =
"https://www.flickr.com/explore/interesting/7days"
static final String WORK_DIR = "./work"
final File workDir = new File(WORK_DIR)
@Scheduled(fixedRate = 30000L)
void downloadFlickrInteresting() {
def photos = extractPhotosFromFlickr()
withPool {
photos.eachParallel { photoUrl ->
log.info "Downloading photo ${photoUrl}"
def tempFile = download(photoUrl)
FileUtils.moveFileToDirectory(tempFile, workDir, true)
}
}
}
}
FlickrDownloader
@Slf4j
@EnableScheduling
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('commons-io:commons-io:2.4')
@Grab('org.codehaus.gpars:gpars:1.2.1')
class FlickrDownloader {
static final String FLICKER_INTERESTING_URL =
"https://www.flickr.com/explore/interesting/7days"
static final String WORK_DIR = "./work"
final File workDir = new File(WORK_DIR)
@Scheduled(fixedRate = 30000L)
void downloadFlickrInteresting() {
def photos = extractPhotosFromFlickr()
withPool {
photos.eachParallel { photoUrl ->
log.info "Downloading photo ${photoUrl}"
def tempFile = download(photoUrl)
FileUtils.moveFileToDirectory(tempFile, workDir, true)
}
}
}
}
FlickrDownloader
FlickrDownloader
@Slf4j
@EnableScheduling
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('commons-io:commons-io:2.4')
@Grab('org.codehaus.gpars:gpars:1.2.1')
class FlickrDownloader {
static final String FLICKER_INTERESTING_URL =
"https://www.flickr.com/explore/interesting/7days"
static final String WORK_DIR = "./work"
final File workDir = new File(WORK_DIR)
@Scheduled(fixedRate = 30000L)
void downloadFlickrInteresting() {
def photos = extractPhotosFromFlickr()
withPool {
photos.eachParallel { photoUrl ->
log.info "Downloading photo ${photoUrl}"
def tempFile = download(photoUrl)
FileUtils.moveFileToDirectory(tempFile, workDir, true)
}
}
}
}
private List extractPhotosFromFlickr() {
Document doc = Jsoup.connect(FLICKER_INTERESTING_URL).get()
Elements images = doc.select("img.pc_img")
def photos = images
.listIterator()
.collect { it.attr('src').replace('_m.jpg', '_b.jpg') }
photos
}
FlickrDownloader
@Slf4j
@EnableScheduling
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('commons-io:commons-io:2.4')
@Grab('org.codehaus.gpars:gpars:1.2.1')
class FlickrDownloader {
static final String FLICKER_INTERESTING_URL =
"https://www.flickr.com/explore/interesting/7days"
static final String WORK_DIR = "./work"
final File workDir = new File(WORK_DIR)
@Scheduled(fixedRate = 30000L)
void downloadFlickrInteresting() {
def photos = extractPhotosFromFlickr()
withPool {
photos.eachParallel { photoUrl ->
log.info "Downloading photo ${photoUrl}"
def tempFile = download(photoUrl)
FileUtils.moveFileToDirectory(tempFile, workDir, true)
}
}
}
}
private File download(String url) {
def tempFile = File.createTempFile('flickr_downloader', '')
tempFile << url.toURL().bytes
tempFile
}
FlickrDownloader
@Slf4j
@EnableScheduling
@Grab('org.jsoup:jsoup:1.8.1')
@Grab('commons-io:commons-io:2.4')
@Grab('org.codehaus.gpars:gpars:1.2.1')
class FlickrDownloader {
static final String FLICKER_INTERESTING_URL =
"https://www.flickr.com/explore/interesting/7days"
static final String WORK_DIR = "./work"
final File workDir = new File(WORK_DIR)
@Scheduled(fixedRate = 30000L)
void downloadFlickrInteresting() {
def photos = extractPhotosFromFlickr()
withPool {
photos.eachParallel { photoUrl ->
log.info "Downloading photo ${photoUrl}"
def tempFile = download(photoUrl)
FileUtils.moveFileToDirectory(tempFile, workDir, true)
}
}
}
}
FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2015-04-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2015-04-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2015-04-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2015-04-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader
2015-04-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader
2015-04-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader
2015-04-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
2015-04-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
4.
Frontend
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
Fronted
▷ MarkupTemplateEngine (HTML)
▷ Websockets
▷ Grooscript
HTML
yieldUnescaped '<!DOCTYPE html>'
html {
head {
title "Polaromatic"
link(rel: 'stylesheet', href: '/css/app.css')
link(rel: 'stylesheet', href: '/css/gh-fork-ribbon.css')
['webjars/sockjs-client/0.3.4-1/sockjs.min.js',
'webjars/stomp-websocket/2.3.1-1/stomp.min.js',
'webjars/jquery/2.1.3/jquery.min.js',
'webjars/handlebars/2.0.0-1/handlebars.min.js',
'js/Connection.js']
.each {
yieldUnescaped "<script src='$it'></script>"
}
}
}
HTML
body {
...
div(id: 'header') {
div(class: 'center') {
a(href: 'https://github.com/lmivan/contest', target: 'blank') {
img(src: 'images/polaromatic-logo.png')
}
p('Polaromatic')
span('Powered by Spring Boot')
}
}
div(id: 'timeline', class: 'center')
}
script(id: 'photo-template', type: 'text/x-handlebars-template') {
div(class: 'photo-cover') {
div(class: 'photo', style: 'visibility:hidden; height:0') {
img(src: '{{image}}')
}
}
}
yieldUnescaped "<script>Connection().start()</script>"
}
Websockets
@Configuration
@EnableWebSocketMessageBroker
class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker '/notifications'
}
@Override
void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint('/polaromatic').withSockJS()
}
}
Websockets
class BrowserPushService {
@Autowired
SimpMessagingTemplate template
Photo pushToBrowser(Photo photo) {
log.debug "Pushing file to browser: ${photo.output}"
String imageB64 = new File(photo.output).bytes.encodeBase64().toString()
template.convertAndSend "/notifications/photo", imageB64
return photo
}
}
Websockets
class BrowserPushService {
@Autowired
SimpMessagingTemplate template
Photo pushToBrowser(Photo photo) {
log.debug "Pushing file to browser: ${photo.output}"
String imageB64 = new File(photo.output).bytes.encodeBase64().toString()
template.convertAndSend "/notifications/photo", imageB64
return photo
}
}
<chain input-channel="incommingFilesChannel">
<service-activator ref="fileService" method="preprocessFile"/>
<service-activator ref="imageConverterService" method="applyEffect"/>
<service-activator ref="browserPushService" method="pushToBrowser"/>
<service-activator ref="metricsService" method="updateMetrics"/>
<service-activator ref="fileService" method="deleteTempFiles"/>
</chain>
class Connection {
@GsNative
def initOn(source, path) {/*
var socket = new SockJS(path);
return [Handlebars.compile(source), Stomp.over(socket)];
*/}
def start() {
def source = $("#photo-template").html()
def (template, client) = initOn(source, '/polaromatic')
client.debug = null
client.connect(gs.toJavascript([:])) { ->
client.subscribe('/notifications/photo') { message ->
def context = [image: 'data:image/png;base64,' + message.body]
def html = template(context)
$("#timeline").prepend(html)
$("#timeline .photo:first-child img").on("load") {
$(this).parent().css(gs.toJavascript(display: 'none', visibility: 'visible', height: 'auto'))
$(this).parent().slideDown()
}
}
}
}
}
Grooscript (Javascript)
5.
Android
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
Android App
▷ Disclaimer: I'm not an Android developer
▷ Lazybones template (@marioggar)
▷ Traits, @CompileStatic
▷ SwissKnife
Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer
Android
trait Toastable {
@OnUIThread
void showToastMessage(String message) {
Toast toast = Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT)
toast.show()
}
}
Android
trait Toastable {
@OnUIThread
void showToastMessage(String message) {
Toast toast = Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT)
toast.show()
}
}
@CompileStatic
public class ShareActivity extends Activity implements Toastable {
...
showToastMessage(getString(R.string.share_ok_msg))
...
}
6.
Tests
Tests
▷ Spock Framework
▷ 0.7 for more than 3 years
▷ Now 1.0
▷ JUnit compatible
Spock
class BrowserPushServiceSpec extends Specification {
void 'should push a converted photo to the browser'() {
given: 'a photo'
def output = File.createTempFile("output", "")
def photo = new Photo(output: output.path)
and: 'a mocked SimpMessagingTemplate'
def mockSimpMessagingTemplate = Mock(SimpMessagingTemplate)
and: 'the push service'
def browserPushService = new BrowserPushService(template: mockSimpMessagingTemplate)
when: 'pushing the photo to the browser'
browserPushService.pushToBrowser(photo)
then: 'the photo is pushed'
1 * mockSimpMessagingTemplate.convertAndSend('/notifications/photo', "")
}
}
7.
Build tool
Build tool
▷ Gradle
▷ Multiproject to build backend, documentation
and android
Gradle
subprojects {
buildscript {
repositories {
jcenter()
}
}
repositories {
jcenter()
}
}
task wrapper(type: Wrapper) {
gradleVersion = '2.2.1'
}
include 'polaromatic-back'
include 'polaromatic-groid'
include 'polaromatic-docs'
build.gradle settings.gradle
8.
Documentation
Documentation
▷ Asciidoctor (FTW!)
▷ Gradle plugin
▷ Backends: html, epub, pdf,...
Asciidoctor
buildscript {
dependencies {
classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2'
}
}
apply plugin: 'org.asciidoctor.convert'
asciidoctor {
sourceDir 'src/docs'
outputDir "${buildDir}/docs"
attributes 'source-highlighter': 'coderay',
toc : 'left',
icons : 'font'
}
Asciidoctor
[source,xml,indent=0]
.src/main/resources/resources.xml
----
include::{polaromaticBackResources}/resources.xml[tags=appFlow]
----
<1> Define the integration with the file system
<2> Preprocess the file received
<3> Apply the Polaroid effect
<4> Send the new photo to the browser using Websockets
<5> Update the metrics
<6> Delete all temporary files
Asciidoctor
[source,xml,indent=0]
.src/main/resources/resources.xml
----
include::{polaromaticBackResources}/resources.xml[tags=appFlow]
----
<1> Define the integration with the file system
<2> Preprocess the file received
<3> Apply the Polaroid effect
<4> Send the new photo to the browser using Websockets
<5> Update the metrics
<6> Delete all temporary files
<!-- tag::appFlow[] -->
<file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/> <!--1-->
<chain input-channel="incommingFilesChannel">
<service-activator ref="fileService" method="preprocessFile"/> <!--2-->
<service-activator ref="imageConverterService" method="applyEffect"/> <!--3-->
<service-activator ref="browserPushService" method="pushToBrowser"/> <!--4-->
<service-activator ref="metricsService" method="updateMetrics"/> <!--5-->
<service-activator ref="fileService" method="deleteTempFiles"/> <!--6-->
</chain>
<!-- end::appFlow[]-->
Asciidoctor
9.
Summary
“
Groovy, groovy everywhere...
Thanks!
Any questions?
@ilopmar
lopez.ivan@gmail.com
https://github.com/lmivan
Iván López
http://kcy.me/22key

More Related Content

PDF
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
Iván López Martín
 
PDF
GR8Conf 2016 - Mum, I want to be a Groovy full-stack developer
Iván López Martín
 
PDF
Simon Laws – Apache Flink Cluster Deployment on Docker and Docker-Compose
Flink Forward
 
PDF
Git real slides
Lucas Couto
 
PPTX
Ondemand scaling-aws
Iegor Fadieiev
 
PDF
Git Real
Gong Haibing
 
PDF
Making the Most of Your Gradle Build
Andres Almiray
 
KEY
Matt Gauger - Git & Github web414 December 2010
Matt Gauger
 
ConFoo 2016 - Mum, I want to be a Groovy full-stack developer
Iván López Martín
 
GR8Conf 2016 - Mum, I want to be a Groovy full-stack developer
Iván López Martín
 
Simon Laws – Apache Flink Cluster Deployment on Docker and Docker-Compose
Flink Forward
 
Git real slides
Lucas Couto
 
Ondemand scaling-aws
Iegor Fadieiev
 
Git Real
Gong Haibing
 
Making the Most of Your Gradle Build
Andres Almiray
 
Matt Gauger - Git & Github web414 December 2010
Matt Gauger
 

What's hot (13)

PDF
From Spring Boot 2.2 to Spring Boot 2.3 #jsug
Toshiaki Maki
 
PDF
JavaOne 2013: Java 8 - The Good Parts
Konrad Malawski
 
PDF
Moving In: how to port your content from * to Drupal
Emma Jane Hogbin Westby
 
PDF
The JavaFX Ecosystem
Andres Almiray
 
PDF
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
Shift Conference
 
PDF
The Future of Futures - A Talk About Java 8 CompletableFutures
Haim Yadid
 
PDF
手把手教你如何串接 Log 到各種網路服務
Mu Chun Wang
 
PDF
分布式版本管理
jeffz
 
PDF
Angular Optimization Web Performance Meetup
David Barreto
 
PPTX
Riga Dev Day - Automated Android Continuous Integration
Nicolas Fränkel
 
PDF
Presente e Futuro: Java EE.next()
Bruno Borges
 
PDF
Ditching JQuery
howlowck
 
PPTX
Spring Boot and REST API
07.pallav
 
From Spring Boot 2.2 to Spring Boot 2.3 #jsug
Toshiaki Maki
 
JavaOne 2013: Java 8 - The Good Parts
Konrad Malawski
 
Moving In: how to port your content from * to Drupal
Emma Jane Hogbin Westby
 
The JavaFX Ecosystem
Andres Almiray
 
Shift Remote: Mobile - Devops-ify your life with Github Actions - Nicola Cort...
Shift Conference
 
The Future of Futures - A Talk About Java 8 CompletableFutures
Haim Yadid
 
手把手教你如何串接 Log 到各種網路服務
Mu Chun Wang
 
分布式版本管理
jeffz
 
Angular Optimization Web Performance Meetup
David Barreto
 
Riga Dev Day - Automated Android Continuous Integration
Nicolas Fränkel
 
Presente e Futuro: Java EE.next()
Bruno Borges
 
Ditching JQuery
howlowck
 
Spring Boot and REST API
07.pallav
 
Ad

Similar to Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer (10)

PDF
Mum, I want to be a Groovy full-stack developer
GR8Conf
 
PDF
Re-Design with Elixir/OTP
Mustafa TURAN
 
PDF
OSCON 2005: Build Your Own Chandler Parcel
Ted Leung
 
KEY
爬虫点滴
Open Party
 
PDF
SpringOne 2GX 2015 - Fullstack Groovy developer
Iván López Martín
 
PDF
Crawlware
kidrane
 
PPTX
Pithos - Architecture and .NET Technologies
Panagiotis Kanavos
 
PPT
Collaborative Workflow Development and Experimentation in the Digital Humanities
cneudecker
 
PPTX
d.mix: Programming by a Sample
Leslie W
 
PDF
GR8ConfUS 2017 - Real Time Traffic Visualization with Vizceral and Hystrix
Roberto Pérez Alcolea
 
Mum, I want to be a Groovy full-stack developer
GR8Conf
 
Re-Design with Elixir/OTP
Mustafa TURAN
 
OSCON 2005: Build Your Own Chandler Parcel
Ted Leung
 
爬虫点滴
Open Party
 
SpringOne 2GX 2015 - Fullstack Groovy developer
Iván López Martín
 
Crawlware
kidrane
 
Pithos - Architecture and .NET Technologies
Panagiotis Kanavos
 
Collaborative Workflow Development and Experimentation in the Digital Humanities
cneudecker
 
d.mix: Programming by a Sample
Leslie W
 
GR8ConfUS 2017 - Real Time Traffic Visualization with Vizceral and Hystrix
Roberto Pérez Alcolea
 
Ad

More from Iván López Martín (20)

PDF
CommitConf 2025 - Spring AI: IA Avanzada para desarrolladores Spring
Iván López Martín
 
PDF
SalmorejoTech 2024 - Spring Boot <3 Testcontainers
Iván López Martín
 
PDF
CommitConf 2024 - Spring Boot <3 Testcontainers
Iván López Martín
 
PDF
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Iván López Martín
 
PDF
VMware - Testcontainers y Spring Boot
Iván López Martín
 
PDF
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Iván López Martín
 
PDF
Codemotion Madrid 2023 - Testcontainers y Spring Boot
Iván López Martín
 
PDF
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
Iván López Martín
 
PDF
Construyendo un API REST con Spring Boot y GraalVM
Iván López Martín
 
PDF
jLove 2020 - Micronaut and graalvm: The power of AoT
Iván López Martín
 
PDF
Codemotion Madrid 2020 - Serverless con Micronaut
Iván López Martín
 
PDF
JConf Perú 2020 - ¡Micronaut en acción!
Iván López Martín
 
PDF
JConf Perú 2020 - Micronaut + GraalVM = <3
Iván López Martín
 
PDF
JConf México 2020 - Micronaut + GraalVM = <3
Iván López Martín
 
PDF
Developing Micronaut Applications With IntelliJ IDEA
Iván López Martín
 
PDF
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
Iván López Martín
 
PDF
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Iván López Martín
 
PDF
Greach 2019 - Creating Micronaut Configurations
Iván López Martín
 
PDF
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
Iván López Martín
 
PDF
JavaDay Lviv 2019 - Micronaut in action!
Iván López Martín
 
CommitConf 2025 - Spring AI: IA Avanzada para desarrolladores Spring
Iván López Martín
 
SalmorejoTech 2024 - Spring Boot <3 Testcontainers
Iván López Martín
 
CommitConf 2024 - Spring Boot <3 Testcontainers
Iván López Martín
 
Voxxed Days CERN 2024 - Spring Boot <3 Testcontainers.pdf
Iván López Martín
 
VMware - Testcontainers y Spring Boot
Iván López Martín
 
Spring IO 2023 - Dynamic OpenAPIs with Spring Cloud Gateway
Iván López Martín
 
Codemotion Madrid 2023 - Testcontainers y Spring Boot
Iván López Martín
 
CommitConf 2023 - Spring Framework 6 y Spring Boot 3
Iván López Martín
 
Construyendo un API REST con Spring Boot y GraalVM
Iván López Martín
 
jLove 2020 - Micronaut and graalvm: The power of AoT
Iván López Martín
 
Codemotion Madrid 2020 - Serverless con Micronaut
Iván López Martín
 
JConf Perú 2020 - ¡Micronaut en acción!
Iván López Martín
 
JConf Perú 2020 - Micronaut + GraalVM = <3
Iván López Martín
 
JConf México 2020 - Micronaut + GraalVM = <3
Iván López Martín
 
Developing Micronaut Applications With IntelliJ IDEA
Iván López Martín
 
CommitConf 2019 - Micronaut y GraalVm: La combinación perfecta
Iván López Martín
 
Codemotion Madrid 2019 - ¡GraalVM y Micronaut: compañeros perfectos!
Iván López Martín
 
Greach 2019 - Creating Micronaut Configurations
Iván López Martín
 
VoxxedDays Bucharest 2019 - Alexa, nice to meet you
Iván López Martín
 
JavaDay Lviv 2019 - Micronaut in action!
Iván López Martín
 

Recently uploaded (20)

PDF
Make GenAI investments go further with the Dell AI Factory
Principled Technologies
 
PDF
SparkLabs Primer on Artificial Intelligence 2025
SparkLabs Group
 
PDF
Responsible AI and AI Ethics - By Sylvester Ebhonu
Sylvester Ebhonu
 
PDF
The Future of Artificial Intelligence (AI)
Mukul
 
PDF
How-Cloud-Computing-Impacts-Businesses-in-2025-and-Beyond.pdf
Artjoker Software Development Company
 
PDF
Brief History of Internet - Early Days of Internet
sutharharshit158
 
PPTX
OA presentation.pptx OA presentation.pptx
pateldhruv002338
 
PDF
Software Development Methodologies in 2025
KodekX
 
PDF
Doc9.....................................
SofiaCollazos
 
PDF
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
PDF
How Open Source Changed My Career by abdelrahman ismail
a0m0rajab1
 
PPTX
New ThousandEyes Product Innovations: Cisco Live June 2025
ThousandEyes
 
PPTX
The-Ethical-Hackers-Imperative-Safeguarding-the-Digital-Frontier.pptx
sujalchauhan1305
 
PPTX
cloud computing vai.pptx for the project
vaibhavdobariyal79
 
PDF
Event Presentation Google Cloud Next Extended 2025
minhtrietgect
 
PDF
Presentation about Hardware and Software in Computer
snehamodhawadiya
 
PPTX
Dev Dives: Automate, test, and deploy in one place—with Unified Developer Exp...
AndreeaTom
 
PDF
BLW VOCATIONAL TRAINING SUMMER INTERNSHIP REPORT
codernjn73
 
PDF
NewMind AI Weekly Chronicles - July'25 - Week IV
NewMind AI
 
PPTX
AI in Daily Life: How Artificial Intelligence Helps Us Every Day
vanshrpatil7
 
Make GenAI investments go further with the Dell AI Factory
Principled Technologies
 
SparkLabs Primer on Artificial Intelligence 2025
SparkLabs Group
 
Responsible AI and AI Ethics - By Sylvester Ebhonu
Sylvester Ebhonu
 
The Future of Artificial Intelligence (AI)
Mukul
 
How-Cloud-Computing-Impacts-Businesses-in-2025-and-Beyond.pdf
Artjoker Software Development Company
 
Brief History of Internet - Early Days of Internet
sutharharshit158
 
OA presentation.pptx OA presentation.pptx
pateldhruv002338
 
Software Development Methodologies in 2025
KodekX
 
Doc9.....................................
SofiaCollazos
 
Research-Fundamentals-and-Topic-Development.pdf
ayesha butalia
 
How Open Source Changed My Career by abdelrahman ismail
a0m0rajab1
 
New ThousandEyes Product Innovations: Cisco Live June 2025
ThousandEyes
 
The-Ethical-Hackers-Imperative-Safeguarding-the-Digital-Frontier.pptx
sujalchauhan1305
 
cloud computing vai.pptx for the project
vaibhavdobariyal79
 
Event Presentation Google Cloud Next Extended 2025
minhtrietgect
 
Presentation about Hardware and Software in Computer
snehamodhawadiya
 
Dev Dives: Automate, test, and deploy in one place—with Unified Developer Exp...
AndreeaTom
 
BLW VOCATIONAL TRAINING SUMMER INTERNSHIP REPORT
codernjn73
 
NewMind AI Weekly Chronicles - July'25 - Week IV
NewMind AI
 
AI in Daily Life: How Artificial Intelligence Helps Us Every Day
vanshrpatil7
 

Spring I/O 2015 - Mum, I want to be a Groovy full-stack developer

  • 1. Mum, I want to be a Groovy full-stack developer @ilopmarIván López
  • 2. Hello! I am Iván López @ilopmar http://greachconf.com@madridgug
  • 3. Thank you very much! Q&A
  • 16. Polaromatic ▷ Spring Boot ▷ Core App ▷ Spring MVC ▷ Spring Integration Flow
  • 17. <file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/> <chain input-channel="incommingFilesChannel"> <service-activator ref="fileService" method="preprocessFile"/> <service-activator ref="imageConverterService" method="applyEffect"/> <service-activator ref="browserPushService" method="pushToBrowser"/> <service-activator ref="metricsService" method="updateMetrics"/> <service-activator ref="fileService" method="deleteTempFiles"/> </chain> Spring Integration Flow
  • 18. <file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/> <chain input-channel="incommingFilesChannel"> <service-activator ref="fileService" method="preprocessFile"/> <service-activator ref="imageConverterService" method="applyEffect"/> <service-activator ref="browserPushService" method="pushToBrowser"/> <service-activator ref="metricsService" method="updateMetrics"/> <service-activator ref="fileService" method="deleteTempFiles"/> </chain> Spring Integration Flow Photo preprocessFile(File file) { def pr = new PolaroidRequest(file) this.preprocessFile(pr) } File service
  • 19. <file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/> <chain input-channel="incommingFilesChannel"> <service-activator ref="fileService" method="preprocessFile"/> <service-activator ref="imageConverterService" method="applyEffect"/> <service-activator ref="browserPushService" method="pushToBrowser"/> <service-activator ref="metricsService" method="updateMetrics"/> <service-activator ref="fileService" method="deleteTempFiles"/> </chain> Spring Integration Flow File service Photo preprocessFile(File file) { def pr = new PolaroidRequest(file) this.preprocessFile(pr) } Photo preprocessFile(PolaroidRequest polaroidRequest) { String outputFile = File.createTempFile("output", ".png").path return new Photo(input: polaroidRequest.inputFile.absolutePath, output: outputFile, text: polaroidRequest.text) }
  • 20. class ImageConverterService { private static final String DEFAULT_CAPTION = "#LearningSpringBoot with Polaromaticn" Random rnd = new Random() Photo applyEffect(Photo photo) { log.debug "Applying effect to file: ${photo.input}..." def inputFile = photo.input def outputFile = photo.output double polaroidRotation = rnd.nextInt(6).toDouble() String caption = photo.text ?: DEFAULT_CAPTION def op = new IMOperation() op.addImage(inputFile) op.thumbnail(300, 300) .set("caption", caption) .gravity("center") .pointsize(20) .background("black") .polaroid(rnd.nextBoolean() ? polaroidRotation : -polaroidRotation) .addImage(outputFile) def command = new ConvertCmd() command.run(op) photo } } Image converter
  • 22. FlickrDownloader ▷ Spring Boot CLI ▷ Download Flickr Interesting pictures ▷ Jsoup, GPars ▷ 55 lines of Groovy code (microservice?)
  • 23. @Slf4j @EnableScheduling @Grab('org.jsoup:jsoup:1.8.1') @Grab('commons-io:commons-io:2.4') @Grab('org.codehaus.gpars:gpars:1.2.1') class FlickrDownloader { static final String FLICKER_INTERESTING_URL = "https://www.flickr.com/explore/interesting/7days" static final String WORK_DIR = "./work" final File workDir = new File(WORK_DIR) @Scheduled(fixedRate = 30000L) void downloadFlickrInteresting() { def photos = extractPhotosFromFlickr() withPool { photos.eachParallel { photoUrl -> log.info "Downloading photo ${photoUrl}" def tempFile = download(photoUrl) FileUtils.moveFileToDirectory(tempFile, workDir, true) } } } } FlickrDownloader
  • 24. @Slf4j @EnableScheduling @Grab('org.jsoup:jsoup:1.8.1') @Grab('commons-io:commons-io:2.4') @Grab('org.codehaus.gpars:gpars:1.2.1') class FlickrDownloader { static final String FLICKER_INTERESTING_URL = "https://www.flickr.com/explore/interesting/7days" static final String WORK_DIR = "./work" final File workDir = new File(WORK_DIR) @Scheduled(fixedRate = 30000L) void downloadFlickrInteresting() { def photos = extractPhotosFromFlickr() withPool { photos.eachParallel { photoUrl -> log.info "Downloading photo ${photoUrl}" def tempFile = download(photoUrl) FileUtils.moveFileToDirectory(tempFile, workDir, true) } } } } FlickrDownloader
  • 25. FlickrDownloader @Slf4j @EnableScheduling @Grab('org.jsoup:jsoup:1.8.1') @Grab('commons-io:commons-io:2.4') @Grab('org.codehaus.gpars:gpars:1.2.1') class FlickrDownloader { static final String FLICKER_INTERESTING_URL = "https://www.flickr.com/explore/interesting/7days" static final String WORK_DIR = "./work" final File workDir = new File(WORK_DIR) @Scheduled(fixedRate = 30000L) void downloadFlickrInteresting() { def photos = extractPhotosFromFlickr() withPool { photos.eachParallel { photoUrl -> log.info "Downloading photo ${photoUrl}" def tempFile = download(photoUrl) FileUtils.moveFileToDirectory(tempFile, workDir, true) } } } } private List extractPhotosFromFlickr() { Document doc = Jsoup.connect(FLICKER_INTERESTING_URL).get() Elements images = doc.select("img.pc_img") def photos = images .listIterator() .collect { it.attr('src').replace('_m.jpg', '_b.jpg') } photos }
  • 26. FlickrDownloader @Slf4j @EnableScheduling @Grab('org.jsoup:jsoup:1.8.1') @Grab('commons-io:commons-io:2.4') @Grab('org.codehaus.gpars:gpars:1.2.1') class FlickrDownloader { static final String FLICKER_INTERESTING_URL = "https://www.flickr.com/explore/interesting/7days" static final String WORK_DIR = "./work" final File workDir = new File(WORK_DIR) @Scheduled(fixedRate = 30000L) void downloadFlickrInteresting() { def photos = extractPhotosFromFlickr() withPool { photos.eachParallel { photoUrl -> log.info "Downloading photo ${photoUrl}" def tempFile = download(photoUrl) FileUtils.moveFileToDirectory(tempFile, workDir, true) } } } } private File download(String url) { def tempFile = File.createTempFile('flickr_downloader', '') tempFile << url.toURL().bytes tempFile }
  • 27. FlickrDownloader @Slf4j @EnableScheduling @Grab('org.jsoup:jsoup:1.8.1') @Grab('commons-io:commons-io:2.4') @Grab('org.codehaus.gpars:gpars:1.2.1') class FlickrDownloader { static final String FLICKER_INTERESTING_URL = "https://www.flickr.com/explore/interesting/7days" static final String WORK_DIR = "./work" final File workDir = new File(WORK_DIR) @Scheduled(fixedRate = 30000L) void downloadFlickrInteresting() { def photos = extractPhotosFromFlickr() withPool { photos.eachParallel { photoUrl -> log.info "Downloading photo ${photoUrl}" def tempFile = download(photoUrl) FileUtils.moveFileToDirectory(tempFile, workDir, true) } } } }
  • 28. FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2015-04-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2015-04-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2015-04-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
  • 29. FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.139 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2015-04-17 21:56:11.354 INFO 16447 --- [111617-worker-1] polaromatic.FlickrDownloader 2015-04-17 21:56:11.375 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.527 INFO 16447 --- [111617-worker-3] polaromatic.FlickrDownloader 2015-04-17 21:56:11.537 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.612 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 21:56:11.693 INFO 16447 --- [111617-worker-2] polaromatic.FlickrDownloader 2015-04-17 22:02:17.019 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:19.451 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:21.661 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:22.079 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:22.877 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:23.392 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:23.749 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:24.250 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader 2015-04-17 22:02:24.695 INFO 9872 --- [pool-1-thread-1] polaromatic.FlickrDownloader
  • 32. Fronted ▷ MarkupTemplateEngine (HTML) ▷ Websockets ▷ Grooscript
  • 33. HTML yieldUnescaped '<!DOCTYPE html>' html { head { title "Polaromatic" link(rel: 'stylesheet', href: '/css/app.css') link(rel: 'stylesheet', href: '/css/gh-fork-ribbon.css') ['webjars/sockjs-client/0.3.4-1/sockjs.min.js', 'webjars/stomp-websocket/2.3.1-1/stomp.min.js', 'webjars/jquery/2.1.3/jquery.min.js', 'webjars/handlebars/2.0.0-1/handlebars.min.js', 'js/Connection.js'] .each { yieldUnescaped "<script src='$it'></script>" } } }
  • 34. HTML body { ... div(id: 'header') { div(class: 'center') { a(href: 'https://github.com/lmivan/contest', target: 'blank') { img(src: 'images/polaromatic-logo.png') } p('Polaromatic') span('Powered by Spring Boot') } } div(id: 'timeline', class: 'center') } script(id: 'photo-template', type: 'text/x-handlebars-template') { div(class: 'photo-cover') { div(class: 'photo', style: 'visibility:hidden; height:0') { img(src: '{{image}}') } } } yieldUnescaped "<script>Connection().start()</script>" }
  • 35. Websockets @Configuration @EnableWebSocketMessageBroker class WebsocketConfig extends AbstractWebSocketMessageBrokerConfigurer { @Override void configureMessageBroker(MessageBrokerRegistry config) { config.enableSimpleBroker '/notifications' } @Override void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint('/polaromatic').withSockJS() } }
  • 36. Websockets class BrowserPushService { @Autowired SimpMessagingTemplate template Photo pushToBrowser(Photo photo) { log.debug "Pushing file to browser: ${photo.output}" String imageB64 = new File(photo.output).bytes.encodeBase64().toString() template.convertAndSend "/notifications/photo", imageB64 return photo } }
  • 37. Websockets class BrowserPushService { @Autowired SimpMessagingTemplate template Photo pushToBrowser(Photo photo) { log.debug "Pushing file to browser: ${photo.output}" String imageB64 = new File(photo.output).bytes.encodeBase64().toString() template.convertAndSend "/notifications/photo", imageB64 return photo } } <chain input-channel="incommingFilesChannel"> <service-activator ref="fileService" method="preprocessFile"/> <service-activator ref="imageConverterService" method="applyEffect"/> <service-activator ref="browserPushService" method="pushToBrowser"/> <service-activator ref="metricsService" method="updateMetrics"/> <service-activator ref="fileService" method="deleteTempFiles"/> </chain>
  • 38. class Connection { @GsNative def initOn(source, path) {/* var socket = new SockJS(path); return [Handlebars.compile(source), Stomp.over(socket)]; */} def start() { def source = $("#photo-template").html() def (template, client) = initOn(source, '/polaromatic') client.debug = null client.connect(gs.toJavascript([:])) { -> client.subscribe('/notifications/photo') { message -> def context = [image: 'data:image/png;base64,' + message.body] def html = template(context) $("#timeline").prepend(html) $("#timeline .photo:first-child img").on("load") { $(this).parent().css(gs.toJavascript(display: 'none', visibility: 'visible', height: 'auto')) $(this).parent().slideDown() } } } } } Grooscript (Javascript)
  • 41. Android App ▷ Disclaimer: I'm not an Android developer ▷ Lazybones template (@marioggar) ▷ Traits, @CompileStatic ▷ SwissKnife
  • 43. Android trait Toastable { @OnUIThread void showToastMessage(String message) { Toast toast = Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT) toast.show() } }
  • 44. Android trait Toastable { @OnUIThread void showToastMessage(String message) { Toast toast = Toast.makeText(applicationContext, message, Toast.LENGTH_SHORT) toast.show() } } @CompileStatic public class ShareActivity extends Activity implements Toastable { ... showToastMessage(getString(R.string.share_ok_msg)) ... }
  • 46. Tests ▷ Spock Framework ▷ 0.7 for more than 3 years ▷ Now 1.0 ▷ JUnit compatible
  • 47. Spock class BrowserPushServiceSpec extends Specification { void 'should push a converted photo to the browser'() { given: 'a photo' def output = File.createTempFile("output", "") def photo = new Photo(output: output.path) and: 'a mocked SimpMessagingTemplate' def mockSimpMessagingTemplate = Mock(SimpMessagingTemplate) and: 'the push service' def browserPushService = new BrowserPushService(template: mockSimpMessagingTemplate) when: 'pushing the photo to the browser' browserPushService.pushToBrowser(photo) then: 'the photo is pushed' 1 * mockSimpMessagingTemplate.convertAndSend('/notifications/photo', "") } }
  • 49. Build tool ▷ Gradle ▷ Multiproject to build backend, documentation and android
  • 50. Gradle subprojects { buildscript { repositories { jcenter() } } repositories { jcenter() } } task wrapper(type: Wrapper) { gradleVersion = '2.2.1' } include 'polaromatic-back' include 'polaromatic-groid' include 'polaromatic-docs' build.gradle settings.gradle
  • 52. Documentation ▷ Asciidoctor (FTW!) ▷ Gradle plugin ▷ Backends: html, epub, pdf,...
  • 53. Asciidoctor buildscript { dependencies { classpath 'org.asciidoctor:asciidoctor-gradle-plugin:1.5.2' } } apply plugin: 'org.asciidoctor.convert' asciidoctor { sourceDir 'src/docs' outputDir "${buildDir}/docs" attributes 'source-highlighter': 'coderay', toc : 'left', icons : 'font' }
  • 54. Asciidoctor [source,xml,indent=0] .src/main/resources/resources.xml ---- include::{polaromaticBackResources}/resources.xml[tags=appFlow] ---- <1> Define the integration with the file system <2> Preprocess the file received <3> Apply the Polaroid effect <4> Send the new photo to the browser using Websockets <5> Update the metrics <6> Delete all temporary files
  • 55. Asciidoctor [source,xml,indent=0] .src/main/resources/resources.xml ---- include::{polaromaticBackResources}/resources.xml[tags=appFlow] ---- <1> Define the integration with the file system <2> Preprocess the file received <3> Apply the Polaroid effect <4> Send the new photo to the browser using Websockets <5> Update the metrics <6> Delete all temporary files <!-- tag::appFlow[] --> <file:inbound-channel-adapter directory="work" channel="incommingFilesChannel"/> <!--1--> <chain input-channel="incommingFilesChannel"> <service-activator ref="fileService" method="preprocessFile"/> <!--2--> <service-activator ref="imageConverterService" method="applyEffect"/> <!--3--> <service-activator ref="browserPushService" method="pushToBrowser"/> <!--4--> <service-activator ref="metricsService" method="updateMetrics"/> <!--5--> <service-activator ref="fileService" method="deleteTempFiles"/> <!--6--> </chain> <!-- end::appFlow[]-->