├── kue-core ├── src │ ├── config │ │ ├── local.json │ │ └── docker.json │ └── main │ │ ├── java │ │ └── io │ │ │ └── vertx │ │ │ └── blueprint │ │ │ └── kue │ │ │ ├── package-info.java │ │ │ ├── queue │ │ │ ├── package-info.java │ │ │ ├── JobState.java │ │ │ ├── Priority.java │ │ │ ├── KueVerticle.java │ │ │ └── KueWorker.java │ │ │ ├── service │ │ │ ├── package-info.java │ │ │ ├── JobService.java │ │ │ └── impl │ │ │ │ └── JobServiceImpl.java │ │ │ ├── CallbackKue.java │ │ │ ├── util │ │ │ └── RedisHelper.java │ │ │ ├── CallbackKueImpl.java │ │ │ └── Kue.java │ │ ├── asciidoc │ │ ├── enums.adoc │ │ └── dataobjects.adoc │ │ └── generated │ │ └── io │ │ └── vertx │ │ └── blueprint │ │ └── kue │ │ ├── rxjava │ │ └── CallbackKue.java │ │ ├── queue │ │ └── JobConverter.java │ │ └── service │ │ ├── JobServiceVertxProxyHandler.java │ │ └── rxjava │ │ └── JobService.java └── Dockerfile ├── kue-example ├── src │ ├── config │ │ └── local.json │ └── main │ │ └── java │ │ └── io │ │ └── vertx │ │ └── blueprint │ │ └── kue │ │ └── example │ │ ├── VideoProcessVerticle.java │ │ ├── DelayedEmailVerticle.java │ │ ├── ManyJobsProcessingVerticle.java │ │ └── LearningVertxVerticle.java └── README.md ├── kue-http ├── src │ ├── main │ │ └── resources │ │ │ └── webroot │ │ │ ├── views │ │ │ ├── _row.jade │ │ │ ├── _search.jade │ │ │ ├── _sort.jade │ │ │ ├── _filter.jade │ │ │ ├── job │ │ │ │ └── list.jade │ │ │ ├── _menu.jade │ │ │ ├── layout.jade │ │ │ └── _job.jade │ │ │ └── public │ │ │ ├── images │ │ │ └── bg.jpg │ │ │ ├── stylesheets │ │ │ ├── error.styl │ │ │ ├── mixins.styl │ │ │ ├── actions.styl │ │ │ ├── config.styl │ │ │ ├── context-menu.styl │ │ │ ├── main.styl │ │ │ ├── scrollbar.styl │ │ │ ├── menu.styl │ │ │ ├── job.styl │ │ │ └── main.css │ │ │ └── javascripts │ │ │ ├── search.js │ │ │ ├── jquery.ext.js │ │ │ ├── utils.js │ │ │ ├── loading.js │ │ │ ├── progress.js │ │ │ ├── main.js │ │ │ ├── job.js │ │ │ └── caustic.js │ ├── config │ │ └── docker.json │ └── test │ │ └── java │ │ └── io │ │ └── vertx │ │ └── blueprint │ │ └── kue │ │ └── http │ │ └── KueRestApiTest.java └── Dockerfile ├── .travis.yml ├── docs ├── images │ ├── kue_diagram.png │ ├── vertx_kue_ui_1.png │ ├── job_state_machine.png │ ├── event_emit_state_machine.png │ └── kue_future_based_methods.png ├── zh-cn │ ├── doc-http.zh-cn.toc.md │ ├── doc-core.zh-cn.toc.md │ ├── doc-http.zh-cn.md │ └── vertx-kue-features.zh-cn.md └── en │ ├── doc-http.toc.md │ ├── doc-core.toc.md │ ├── doc-http.md │ └── vertx-kue-features-en.md ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── settings.gradle ├── docker └── docker-compose.yml ├── README.zh-cn.md ├── README.md ├── gradlew.bat ├── gradlew └── LICENSE /kue-core/src/config/local.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /kue-example/src/config/local.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/views/_row.jade: -------------------------------------------------------------------------------- 1 | tr 2 | td.title 3 | td.value -------------------------------------------------------------------------------- /kue-core/src/config/docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "redis.host": "redis", 3 | "redis.port": 6379 4 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | services: 7 | - redis-server -------------------------------------------------------------------------------- /kue-http/src/config/docker.json: -------------------------------------------------------------------------------- 1 | { 2 | "http.port": 8080, 3 | "http.address": "0.0.0.0" 4 | } -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/views/_search.jade: -------------------------------------------------------------------------------- 1 | input#search(type='text', placeholder='Search') -------------------------------------------------------------------------------- /docs/images/kue_diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sczyh30/vertx-kue/HEAD/docs/images/kue_diagram.png -------------------------------------------------------------------------------- /docs/images/vertx_kue_ui_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sczyh30/vertx-kue/HEAD/docs/images/vertx_kue_ui_1.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IntelliJ project files 2 | .idea 3 | *.iml 4 | 5 | # build files 6 | .gradle 7 | build 8 | target 9 | .vertx -------------------------------------------------------------------------------- /docs/images/job_state_machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sczyh30/vertx-kue/HEAD/docs/images/job_state_machine.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sczyh30/vertx-kue/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /docs/images/event_emit_state_machine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sczyh30/vertx-kue/HEAD/docs/images/event_emit_state_machine.png -------------------------------------------------------------------------------- /docs/images/kue_future_based_methods.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sczyh30/vertx-kue/HEAD/docs/images/kue_future_based_methods.png -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'vertx-blueprint-job-queue' 2 | 3 | include "kue-core" 4 | include "kue-http" 5 | include "kue-example" -------------------------------------------------------------------------------- /docs/zh-cn/doc-http.zh-cn.toc.md: -------------------------------------------------------------------------------- 1 | - [Vert.x Kue REST API](#vert-x-kue-rest-api) 2 | - [将Kue UI与Vert.x Web进行适配](#将kue-ui与vert-x-web进行适配) 3 | - [展示时间!](#展示时间-) 4 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/views/_sort.jade: -------------------------------------------------------------------------------- 1 | select#sort 2 | option(value='asc') sort 3 | option(value='asc') asc 4 | option(value='desc') desc -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sczyh30/vertx-kue/HEAD/kue-http/src/main/resources/webroot/public/images/bg.jpg -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/views/_filter.jade: -------------------------------------------------------------------------------- 1 | select#filter 2 | option(value='') filter by 3 | - each type in types 4 | option(value=type)= type -------------------------------------------------------------------------------- /docs/en/doc-http.toc.md: -------------------------------------------------------------------------------- 1 | - [Vert.x Kue REST API](#vert-x-kue-rest-api) 2 | - [Adapt Kue UI with Vert.x Web](#adapt-kue-ui-with-vert-x-web) 3 | - [Show time!](#show-time-) 4 | -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/package-info.java: -------------------------------------------------------------------------------- 1 | @ModuleGen(groupPackage = "io.vertx.blueprint.kue", name = "vertx-kue-root-module") 2 | package io.vertx.blueprint.kue; 3 | 4 | import io.vertx.codegen.annotations.ModuleGen; -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/queue/package-info.java: -------------------------------------------------------------------------------- 1 | @ModuleGen(groupPackage = "io.vertx.blueprint.kue.queue", name = "vertx-kue-queue-core-module") 2 | package io.vertx.blueprint.kue.queue; 3 | 4 | import io.vertx.codegen.annotations.ModuleGen; -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/service/package-info.java: -------------------------------------------------------------------------------- 1 | @ModuleGen(groupPackage = "io.vertx.blueprint.kue.service", name = "vertx-kue-service-module") 2 | package io.vertx.blueprint.kue.service; 3 | 4 | import io.vertx.codegen.annotations.ModuleGen; -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Mon Sep 12 23:31:20 CST 2016 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.0-bin.zip 7 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/views/job/list.jade: -------------------------------------------------------------------------------- 1 | extends ../layout 2 | 3 | block body 4 | h1 #{state} 5 | 6 | script. 7 | o(function () { 8 | init('#{state}'); 9 | }); 10 | 11 | 12 | #jobs 13 | #loading: canvas(width=50, height=50) 14 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/stylesheets/error.styl: -------------------------------------------------------------------------------- 1 | 2 | #error 3 | fixed: top -50px right 15px 4 | padding: 20px 5 | transition: top 500ms, opacity 500ms 6 | opacity: 0 7 | background: rgba(dark, .2) 8 | border: 1px solid rgba(dark, .3) 9 | border-radius: 5px 10 | color: dark 11 | &.show 12 | top: 15px 13 | opacity: 1 14 | -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/queue/JobState.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.queue; 2 | 3 | import io.vertx.codegen.annotations.VertxGen; 4 | 5 | /** 6 | * Vert.x Blueprint - Job Queue 7 | * Job state enum class 8 | * 9 | * @author Eric Zhao 10 | */ 11 | @VertxGen 12 | public enum JobState { 13 | INACTIVE, 14 | ACTIVE, 15 | COMPLETE, 16 | FAILED, 17 | DELAYED 18 | } 19 | -------------------------------------------------------------------------------- /kue-example/README.md: -------------------------------------------------------------------------------- 1 | # Vert.x Kue Examples 2 | 3 | This part provides some basic examples of Vert.x Kue. 4 | 5 | - `VideoProcessVerticle`: a simple example simulating video conversion processing 6 | - `DelayedEmailVerticle`: illustrates how to use delayed jobs 7 | - `LearningVertxVerticle`: demonstrates the job and queue events 8 | - `ManyJobsProcessingVerticle`: illustrates many jobs generating and processing 9 | -------------------------------------------------------------------------------- /kue-core/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8-jre 2 | 3 | ENV VERTICLE_FILE build/libs/vertx-blueprint-kue-core.jar 4 | 5 | # Set the location of the verticles 6 | ENV VERTICLE_HOME /usr/verticles 7 | 8 | COPY $VERTICLE_FILE $VERTICLE_HOME/ 9 | COPY src/config/docker.json $VERTICLE_HOME/ 10 | 11 | WORKDIR $VERTICLE_HOME 12 | ENTRYPOINT ["sh", "-c"] 13 | CMD ["java -jar vertx-blueprint-kue-core.jar -cluster -conf docker.json"] -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/stylesheets/mixins.styl: -------------------------------------------------------------------------------- 1 | reset-list() 2 | margin: 0 3 | padding: 0 4 | li 5 | margin: 0 6 | list-style: none 7 | 8 | decorated-box() 9 | border: 1px solid #eee 10 | border-bottom-color: rgba(black, .25) 11 | border-left-color: rgba(black, .2) 12 | border-right-color: rgba(black, .2) 13 | box-shadow: 0 2px 2px 0 rgba(black, .1) 14 | border-radius: 4px 15 | -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "2" 2 | services: 3 | redis: 4 | image: redis:latest 5 | expose: 6 | - "6379" 7 | ports: 8 | - "6379:6379" 9 | vertx-kue-core: 10 | build: ../kue-core/ 11 | depends_on: 12 | - redis 13 | links: 14 | - redis 15 | vertx-kue-http: 16 | build: ../kue-http/ 17 | depends_on: 18 | - vertx-kue-core 19 | ports: 20 | - "8080:8080" -------------------------------------------------------------------------------- /kue-http/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM java:8-jre 2 | 3 | ENV VERTICLE_FILE build/libs/vertx-blueprint-kue-http.jar 4 | 5 | # Set the location of the verticles 6 | ENV VERTICLE_HOME /usr/verticles 7 | 8 | EXPOSE 8080 9 | 10 | COPY $VERTICLE_FILE $VERTICLE_HOME/ 11 | COPY src/config/docker.json $VERTICLE_HOME/ 12 | 13 | WORKDIR $VERTICLE_HOME 14 | ENTRYPOINT ["sh", "-c"] 15 | CMD ["java -jar vertx-blueprint-kue-http.jar -cluster -conf docker.json"] -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/stylesheets/actions.styl: -------------------------------------------------------------------------------- 1 | 2 | #actions 3 | fixed: top -2px right -2px 4 | z-index: 20 5 | 6 | #sort 7 | #filter 8 | #search 9 | float: left 10 | margin: 0 11 | padding: 5px 10px 12 | border: 1px solid #eee 13 | border-radius: 0 0 0 5px 14 | -webkit-appearance: none 15 | color: dark 16 | outline: none 17 | &:hover 18 | border-color: #eee - 10% 19 | 20 | #sort 21 | #filter 22 | cursor: pointer 23 | 24 | #sort 25 | #filter 26 | border-radius: 0 27 | border-left: none -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/views/_menu.jade: -------------------------------------------------------------------------------- 1 | ul#menu 2 | li.inactive 3 | a(href='./inactive') 4 | .count 0 5 | | Queued 6 | li.active 7 | a.active(href='./active') 8 | .count 0 9 | | Active 10 | li.failed 11 | a(href='./failed') 12 | .count 0 13 | | Failed 14 | li.complete 15 | a(href='./complete') 16 | .count 0 17 | | Complete 18 | li.delayed 19 | a(href='./delayed') 20 | .count 0 21 | | Delayed 22 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/stylesheets/config.styl: -------------------------------------------------------------------------------- 1 | // general colors 2 | 3 | dark = #3b3b3b 4 | light = #666 5 | lighter = #777 6 | 7 | bg = #fff 8 | 9 | // status colors 10 | 11 | inactive-color = #00CCCC 12 | complete-color = #00CC7A 13 | active-color = #CCC500 14 | failed-color = #c00 15 | 16 | // menu config 17 | 18 | menu-bg = dark 19 | menu-fg = lighter 20 | 21 | menu-intensity = 13% 22 | menu-colored = false 23 | 24 | // job config 25 | 26 | job-bg = white 27 | 28 | // scrollbar 29 | 30 | scroll-bg = transparent 31 | scroll-thumb = menu-bg 32 | scroll-width = 6px 33 | -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/queue/Priority.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.queue; 2 | 3 | import io.vertx.codegen.annotations.VertxGen; 4 | 5 | /** 6 | * Vert.x Blueprint - Job Queue 7 | * Job priority enum class 8 | * 9 | * @author Eric Zhao 10 | */ 11 | @VertxGen 12 | public enum Priority { 13 | 14 | LOW(10), 15 | NORMAL(0), 16 | MEDIUM(-5), 17 | HIGH(-10), 18 | CRITICAL(-15); 19 | 20 | private int value; 21 | 22 | Priority(int value) { 23 | this.value = value; 24 | } 25 | 26 | public int getValue() { 27 | return value; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/javascripts/search.js: -------------------------------------------------------------------------------- 1 | o(function () { 2 | var search = o('#search'); 3 | search.keyup(function () { 4 | var val = search.val().trim() 5 | , jobs = o('#jobs .job'); 6 | 7 | // show all 8 | if (val.length < 2) return jobs.show(); 9 | 10 | // query 11 | o.get('./job/search/' + encodeURIComponent(val), function (ids) { 12 | jobs.each(function (i, el) { 13 | var id = el.id.replace('job-', ''); 14 | if (~ids.indexOf(id)) { 15 | o(el).show(); 16 | } else { 17 | o(el).hide(); 18 | } 19 | }); 20 | }); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/stylesheets/context-menu.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | highlight-color = #00B3E9 4 | 5 | .context-menu 6 | display: none 7 | reset-list() 8 | decorated-box() 9 | li 10 | &:last-child a 11 | border-bottom: none 12 | a 13 | display: block 14 | background: white 15 | padding: 5px 10px 16 | border: 1px solid transparent 17 | border-bottom: 1px solid #eee 18 | font-size: 12px 19 | &:hover 20 | background: linear-gradient(bottom, highlight-color, highlight-color + 50%) 21 | color: white 22 | border: 1px solid white 23 | &:active 24 | background: linear-gradient(bottom, highlight-color + 10%, highlight-color + 10% + 50%) 25 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/javascripts/jquery.ext.js: -------------------------------------------------------------------------------- 1 | // proxy to allow formatting 2 | // and because $ is ugly 3 | 4 | var o = function (val) { 5 | var args = arguments 6 | , options = args[1] 7 | , i = 0; 8 | 9 | if ('string' != typeof val) return $(val); 10 | if (!~val.indexOf('<')) return $(val); 11 | 12 | val = val.replace(/%([sd])/g, function (_, specifier) { 13 | var arg = args[++i]; 14 | switch (specifier) { 15 | case 's': 16 | return String(arg) 17 | case 'd': 18 | return arg | 0; 19 | } 20 | }); 21 | 22 | val = val.replace(/\{(\w+)\}/g, function (_, name) { 23 | return options[name]; 24 | }); 25 | 26 | return $(val); 27 | }; 28 | 29 | for (var key in $) o[key] = $[key]; -------------------------------------------------------------------------------- /kue-core/src/main/asciidoc/enums.adoc: -------------------------------------------------------------------------------- 1 | = Enums 2 | 3 | [[JobState]] 4 | == JobState 5 | 6 | ++++ 7 | Vert.x Blueprint - Job Queue 8 | Job state enum class 9 | ++++ 10 | ''' 11 | 12 | [cols=">25%,75%"] 13 | [frame="topbot"] 14 | |=== 15 | ^|Name | Description 16 | |[[INACTIVE]]`INACTIVE`|- 17 | |[[ACTIVE]]`ACTIVE`|- 18 | |[[COMPLETE]]`COMPLETE`|- 19 | |[[FAILED]]`FAILED`|- 20 | |[[DELAYED]]`DELAYED`|- 21 | |=== 22 | 23 | [[Priority]] 24 | == Priority 25 | 26 | ++++ 27 | Vert.x Blueprint - Job Queue 28 | Job priority enum class 29 | ++++ 30 | ''' 31 | 32 | [cols=">25%,75%"] 33 | [frame="topbot"] 34 | |=== 35 | ^|Name | Description 36 | |[[LOW]]`LOW`|- 37 | |[[NORMAL]]`NORMAL`|- 38 | |[[MEDIUM]]`MEDIUM`|- 39 | |[[HIGH]]`HIGH`|- 40 | |[[CRITICAL]]`CRITICAL`|- 41 | |=== 42 | 43 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/stylesheets/main.styl: -------------------------------------------------------------------------------- 1 | font-smoothing() 2 | -webkit-font-smoothing: arguments 3 | 4 | @import 'nib' 5 | @import 'config' 6 | @import 'scrollbar' 7 | @import 'menu' 8 | @import 'context-menu' 9 | @import 'job' 10 | @import 'actions' 11 | @import 'error' 12 | 13 | body 14 | font: 13px "helvetica neue", helvetica, arial, sans-serif 15 | font-smoothing: antialiased 16 | background: bg 17 | color: light 18 | 19 | h1, h2, h3 20 | margin: 0 0 25px 0 21 | padding: 0 22 | font-weight: normal 23 | text-transform: capitalize 24 | color: light 25 | 26 | h2 27 | font-size: 16px 28 | margin-top: 20px 29 | 30 | button 31 | a.button 32 | input[type='submit'] 33 | bold-button(glow:#00ABFA) 34 | 35 | pre 36 | margin-top: 20px 37 | 38 | a 39 | text-decoration: none 40 | cursor: pointer 41 | 42 | table 43 | reset-table() 44 | tr td 45 | padding: 2px 5px 46 | 47 | #loading 48 | width: 100% 49 | text-align: center 50 | margin-top: 40px 51 | margin-left: 20px 52 | canvas 53 | margin: 0 auto 54 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/views/layout.jade: -------------------------------------------------------------------------------- 1 | html 2 | head 3 | title= title 4 | link(rel='stylesheet', href='./public/stylesheets/main.css') 5 | script(src='./public/javascripts/utils.js') 6 | script(src='./public/javascripts/jquery.min.js') 7 | script(src='./public/javascripts/jquery.ext.js') 8 | script(src='./public/javascripts/caustic.js') 9 | script(src='./public/javascripts/progress.js') 10 | script(src='./public/javascripts/loading.js') 11 | script(src='./public/javascripts/job.js') 12 | script(src='./public/javascripts/search.js') 13 | script(src='./public/javascripts/main.js') 14 | body 15 | include _menu 16 | #actions 17 | include _search 18 | 19 | include _sort 20 | #content 21 | block body 22 | script(type='text/template')#job-template 23 | include _job 24 | script(type='text/template')#row-template 25 | include _row 26 | #error 27 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/stylesheets/scrollbar.styl: -------------------------------------------------------------------------------- 1 | width = scroll-width 2 | pad-x = 60px 3 | pad-y = 40px 4 | 5 | body 6 | padding: 50px 120px 7 | 8 | /* 9 | html 10 | overflow: auto 11 | 12 | body 13 | position: absolute 14 | top: pad-y 15 | left: pad-x 16 | bottom: pad-y 17 | right: pad-x 18 | padding: 0 pad-x 19 | overflow-y: scroll 20 | overflow-x: hidden 21 | 22 | ::-webkit-scrollbar 23 | background: scroll-bg 24 | width: width 25 | 26 | ::-webkit-scrollbar-button:start:decrement 27 | ::-webkit-scrollbar-button:start:increment 28 | display: none 29 | 30 | ::-webkit-scrollbar-track 31 | border-radius: (width / 2) 32 | box-shadow: inset 0 0 1px rgba(black, .2), inset 0 4px 10px rgba(black, .2) 33 | border: 1px solid rgba(white, .5) 34 | 35 | ::-webkit-scrollbar-track-piece 36 | background: transparent 37 | 38 | ::-webkit-scrollbar-thumb:vertical 39 | height: 30px 40 | transition: background-color 300ms ease-out 41 | background: rgba(scroll-thumb, .5) 42 | border-radius: (width / 2) 43 | &:window-inactive 44 | background: rgba(scroll-thumb, .2) 45 | */ -------------------------------------------------------------------------------- /kue-core/src/main/asciidoc/dataobjects.adoc: -------------------------------------------------------------------------------- 1 | = Cheatsheets 2 | 3 | [[Job]] 4 | == Job 5 | 6 | ++++ 7 | Vert.x Kue 8 | Job domain class. 9 | ++++ 10 | ''' 11 | 12 | [cols=">25%,^25%,50%"] 13 | [frame="topbot"] 14 | |=== 15 | ^|Name | Type ^| Description 16 | |[[address_id]]`address_id`|`String`|- 17 | |[[attempts]]`attempts`|`Number (int)`|- 18 | |[[backoff]]`backoff`|`Json object`|- 19 | |[[created_at]]`created_at`|`Number (long)`|- 20 | |[[data]]`data`|`Json object`|- 21 | |[[delay]]`delay`|`Number (long)`|- 22 | |[[duration]]`duration`|`Number (long)`|- 23 | |[[failed_at]]`failed_at`|`Number (long)`|- 24 | |[[id]]`id`|`Number (long)`|- 25 | |[[max_attempts]]`max_attempts`|`Number (int)`|- 26 | |[[priority]]`priority`|`link:enums.html#Priority[Priority]`|- 27 | |[[progress]]`progress`|`Number (int)`|- 28 | |[[promote_at]]`promote_at`|`Number (long)`|- 29 | |[[removeOnComplete]]`removeOnComplete`|`Boolean`|- 30 | |[[result]]`result`|`Json object`|- 31 | |[[started_at]]`started_at`|`Number (long)`|- 32 | |[[state]]`state`|`link:enums.html#JobState[JobState]`|- 33 | |[[ttl]]`ttl`|`Number (int)`|- 34 | |[[type]]`type`|`String`|- 35 | |[[updated_at]]`updated_at`|`Number (long)`|- 36 | |[[zid]]`zid`|`String`|- 37 | |=== 38 | 39 | -------------------------------------------------------------------------------- /docs/zh-cn/doc-core.zh-cn.toc.md: -------------------------------------------------------------------------------- 1 | - [前言](#前言) 2 | - [Vert.x的消息系统](#vert-x的消息系统) 3 | - [发布/订阅模式](#发布-订阅模式) 4 | - [点对点模式](#点对点模式) 5 | - [请求/回应模式](#请求-回应模式) 6 | - [Vert.x Kue 架构设计](#vert-x-kue-架构设计) 7 | - [Vert.x Kue 组件划分](#vert-x-kue-组件划分) 8 | - [Vert.x Kue 核心模块](#vert-x-kue-核心模块) 9 | - [基于Future的异步模式](#基于future的异步模式) 10 | - [Vert.x Kue中的事件](#vert-x-kue中的事件) 11 | - [任务状态](#任务状态) 12 | - [整体设计](#整体设计) 13 | - [项目结构](#项目结构) 14 | - [任务实体 - 不仅仅是一个数据对象](#任务实体-不仅仅是一个数据对象) 15 | - [任务成员属性](#任务成员属性) 16 | - [任务事件辅助函数](#任务事件辅助函数) 17 | - [Redis中的存储形式](#redis中的存储形式) 18 | - [改变任务状态](#改变任务状态) 19 | - [保存任务](#保存任务) 20 | - [移除任务](#移除任务) 21 | - [监听任务事件](#监听任务事件) 22 | - [更新任务进度](#更新任务进度) 23 | - [任务失败以及重试机制](#任务失败以及重试机制) 24 | - [Event Bus 服务 - JobService](#event-bus-服务-jobservice) 25 | - [异步RPC](#异步rpc) 26 | - [异步服务接口](#异步服务接口) 27 | - [任务服务的实现](#任务服务的实现) 28 | - [注册任务服务](#注册任务服务) 29 | - [Kue - 工作队列](#kue-工作队列) 30 | - [基于Future的封装](#基于future的封装) 31 | - [process和processBlocking方法](#process和processblocking方法) 32 | - [监测延时任务](#监测延时任务) 33 | - [CallbackKue - 提供多语言支持](#callbackkue-提供多语言支持) 34 | - [KueWorker - 任务在此处理](#kueworker-任务在此处理) 35 | - [prepareAndStart方法](#prepareandstart方法) 36 | - [使用zpop按照优先级顺序获取任务](#使用zpop按照优先级顺序获取任务) 37 | - [真正的“处理”逻辑](#真正的-处理-逻辑) 38 | - [处理失败了怎么办?](#处理失败了怎么办-) 39 | - [展示时间!](#展示时间-) 40 | - [结束我们的探索之旅!](#完成我们的探索之旅-) 41 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/stylesheets/menu.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | #menu 4 | reset-list() 5 | fixed: top left 6 | height: 100% 7 | width: 80px 8 | background: menu-bg 9 | border-right: 1px solid menu-bg - 40% 10 | box-shadow: 0 0 0 1px rgba(white, .5) 11 | li 12 | position: relative 13 | text-align: center 14 | if menu-colored 15 | &.inactive 16 | border-right: 1px solid inactive-color 17 | &.active 18 | border-right: 1px solid active-color 19 | &.complete 20 | border-right: 1px solid complete-color 21 | &.failed 22 | border-right: 1px solid failed-color 23 | .count 24 | absolute: top 15px left 25 | text-shadow: 1px 1px 1px menu-bg - menu-intensity 26 | width: 100% 27 | color: menu-fg + (menu-intensity / 2) 28 | a 29 | display: block 30 | padding: 40px 0 10px 0 31 | color: menu-fg 32 | border-top: 1px solid menu-bg + menu-intensity 33 | border-bottom: 1px solid menu-bg - menu-intensity 34 | font-size: 12px 35 | background: menu-bg -= 2% 36 | &:hover 37 | background: menu-bg + 5% 38 | &:active 39 | &.active 40 | background: #343434 41 | box-shadow: inset 0 0 3px 2px menu-bg - 30%, inset 0 -5px 10px 2px menu-bg - 15% 42 | border-bottom: 1px solid menu-bg - 40% 43 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/views/_job.jade: -------------------------------------------------------------------------------- 1 | .job 2 | .block.contents 3 | h2.id 4 | a.remove(title='Delete Job') x 5 | a.restart(title='Restart Job') ↻ 6 | canvas.progress(width=50, height=50) 7 | table.meta 8 | tbody 9 | tr 10 | td Type: 11 | td.type 12 | tr 13 | td Title: 14 | td.title 15 | tr 16 | td Error: 17 | td.errorMessage 18 | .details 19 | .data 20 | table.data 21 | tbody 22 | tr 23 | td State: 24 | td.state 25 | tr 26 | td Priority: 27 | td.priority 28 | tr 29 | td Attempts: 30 | td.attempts 31 | tr.time 32 | td Duration: 33 | td.duration 34 | tr.time 35 | td Created: 36 | td.created_at 37 | tr.time 38 | td Updated: 39 | td.updated_at 40 | tr.time 41 | td Failed: 42 | td.failed_at 43 | .error 44 | pre 45 | ul.log 46 | -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/CallbackKue.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue; 2 | 3 | import io.vertx.blueprint.kue.queue.Job; 4 | import io.vertx.blueprint.kue.service.JobService; 5 | import io.vertx.codegen.annotations.Fluent; 6 | import io.vertx.codegen.annotations.VertxGen; 7 | import io.vertx.core.AsyncResult; 8 | import io.vertx.core.Handler; 9 | import io.vertx.core.Vertx; 10 | import io.vertx.core.eventbus.Message; 11 | import io.vertx.core.json.JsonObject; 12 | 13 | 14 | /** 15 | * A callback-based {@link io.vertx.blueprint.kue.Kue} interface for Vert.x Codegen to support polyglot languages. 16 | * 17 | * @author Eric Zhao 18 | */ 19 | @VertxGen 20 | public interface CallbackKue extends JobService { 21 | 22 | static CallbackKue createKue(Vertx vertx, JsonObject config) { 23 | return new CallbackKueImpl(vertx, config); 24 | } 25 | 26 | Job createJob(String type, JsonObject data); 27 | 28 | @Fluent 29 | CallbackKue on(String eventType, Handler> handler); 30 | 31 | @Fluent 32 | CallbackKue saveJob(Job job, Handler> handler); 33 | 34 | @Fluent 35 | CallbackKue jobProgress(Job job, int complete, int total, Handler> handler); 36 | 37 | @Fluent 38 | CallbackKue jobDone(Job job); 39 | 40 | @Fluent 41 | CallbackKue jobDoneFail(Job job, Throwable ex); 42 | 43 | @Fluent 44 | CallbackKue process(String type, int n, Handler handler); 45 | 46 | @Fluent 47 | CallbackKue processBlocking(String type, int n, Handler handler); 48 | } 49 | -------------------------------------------------------------------------------- /README.zh-cn.md: -------------------------------------------------------------------------------- 1 | # Vert.x Kue 2 | 3 | [![Build Status](https://travis-ci.org/sczyh30/vertx-kue.svg?branch=master)](https://travis-ci.org/sczyh30/vertx-kue) 4 | 5 | Vert.x Kue是一个使用Vert.x开发的分布式优先级任务队列,数据存储后端使用的是 **Redis** 。 6 | Vert.x Kue是 [Automattic/kue](https://github.com/Automattic/kue) 的Vert.x实现版本。 7 | 8 | Vert.x Kue同时作为Vert.x Blueprint项目的第二个应用,用于介绍如何开发基于消息的应用。 9 | 10 | ## 特性 11 | 12 | - 优先级任务 13 | - 可延迟的任务 14 | - 同时处理多个任务 15 | - 任务事件以及工作队列事件 16 | - 可选的任务重试机制以及延迟恢复机制 17 | - RESTful API 18 | - 简洁明了的用户界面(基于Automattic/kue UI) 19 | - 任务进度实时展示 20 | - 任务日志 21 | - 基于 `Future` 的异步模式 22 | - 多种语言支持 23 | - 由 **Vert.x** 强力驱动! 24 | 25 | 特性详情请见[Vert.x Kue 特性介绍](docs/zh-cn/vertx-kue-features.zh-cn.md)。 26 | 27 | ## 详细文档教程 28 | 29 | - [Vert.x Kue Core 教程](http://sczyh30.github.io/vertx-kue/cn/kue-core/index.html) 30 | - [Vert.x Kue Web 教程](http://sczyh30.github.io/vertx-kue/cn/kue-http/index.html) 31 | 32 | ## 构建/运行 33 | 34 | 首先构建整个项目: 35 | 36 | gradle build -x test 37 | 38 | 然后不要忘记启动 Redis: 39 | 40 | redis-server 41 | 42 | 然后我们就可以运行我们的示例应用了: 43 | 44 | java -jar kue-core/build/libs/vertx-blueprint-kue-core.jar -cluster 45 | java -jar kue-http/build/libs/vertx-blueprint-kue-http.jar -cluster 46 | java -jar kue-example/build/libs/vertx-blueprint-kue-example.jar -cluster 47 | 48 | 运行成功后,我们可以在浏览器中输入 `http://localhost:8080` 地址来访问Vert.x Kue UI并且查看任务队列的信息了。 49 | 50 | ![](docs/images/vertx_kue_ui_1.png) 51 | 52 | # 架构 53 | 54 | ![Diagram - How Vert.x Kue works](https://raw.githubusercontent.com/sczyh30/vertx-kue/master/docs/images/kue_diagram.png) 55 | -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/queue/KueVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.queue; 2 | 3 | import io.vertx.blueprint.kue.service.JobService; 4 | import io.vertx.blueprint.kue.util.RedisHelper; 5 | import io.vertx.core.AbstractVerticle; 6 | import io.vertx.core.Future; 7 | import io.vertx.core.json.JsonObject; 8 | import io.vertx.core.logging.Logger; 9 | import io.vertx.core.logging.LoggerFactory; 10 | import io.vertx.redis.RedisClient; 11 | import io.vertx.serviceproxy.ProxyHelper; 12 | 13 | 14 | /** 15 | * Vert.x Blueprint - Job Queue 16 | * Kue Verticle 17 | * 18 | * @author Eric Zhao 19 | */ 20 | public class KueVerticle extends AbstractVerticle { 21 | 22 | private static Logger logger = LoggerFactory.getLogger(Job.class); 23 | 24 | public static final String EB_JOB_SERVICE_ADDRESS = "vertx.kue.service.job.internal"; 25 | 26 | private JsonObject config; 27 | private JobService jobService; 28 | 29 | @Override 30 | public void start(Future future) throws Exception { 31 | this.config = config(); 32 | this.jobService = JobService.create(vertx, config); 33 | // create redis client 34 | RedisClient redisClient = RedisHelper.client(vertx, config); 35 | redisClient.ping(pr -> { // test connection 36 | if (pr.succeeded()) { 37 | logger.info("Kue Verticle is running..."); 38 | 39 | // register job service 40 | ProxyHelper.registerService(JobService.class, vertx, jobService, EB_JOB_SERVICE_ADDRESS); 41 | 42 | future.complete(); 43 | } else { 44 | logger.error("oops!", pr.cause()); 45 | future.fail(pr.cause()); 46 | } 47 | }); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /kue-example/src/main/java/io/vertx/blueprint/kue/example/VideoProcessVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.example; 2 | 3 | import io.vertx.blueprint.kue.Kue; 4 | import io.vertx.blueprint.kue.queue.Job; 5 | import io.vertx.blueprint.kue.queue.Priority; 6 | import io.vertx.core.AbstractVerticle; 7 | import io.vertx.core.json.JsonObject; 8 | 9 | /** 10 | * Vert.x Blueprint - Job Queue 11 | * Example - A video conversion process verticle 12 | * 13 | * @author Eric Zhao 14 | */ 15 | public class VideoProcessVerticle extends AbstractVerticle { 16 | 17 | @Override 18 | public void start() throws Exception { 19 | // create our job queue 20 | Kue kue = Kue.createQueue(vertx, config()); 21 | 22 | System.out.println("Creating job: video conversion"); 23 | 24 | JsonObject data = new JsonObject() 25 | .put("title", "converting video to avi") 26 | .put("user", 1) 27 | .put("frames", 200); 28 | 29 | // create an video conversion job 30 | Job job0 = kue.createJob("video conversion", data) 31 | .priority(Priority.NORMAL); 32 | 33 | // save the job 34 | job0.save().setHandler(r0 -> { 35 | if (r0.succeeded()) { 36 | // process logic start (3 at a time) 37 | kue.process("video conversion", 3, job -> { 38 | int frames = job.getData().getInteger("frames", 100); 39 | next(0, frames, job); 40 | }); 41 | // process logic end 42 | } else { 43 | r0.cause().printStackTrace(); 44 | } 45 | }); 46 | } 47 | 48 | // pretend we are doing some work 49 | private void next(int i, int frames, Job job) { 50 | vertx.setTimer(666, r -> { 51 | job.progress(i, frames); 52 | if (i == frames) 53 | job.done(); 54 | else 55 | next(i + 1, frames, job); 56 | }); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /kue-example/src/main/java/io/vertx/blueprint/kue/example/DelayedEmailVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.example; 2 | 3 | import io.vertx.blueprint.kue.Kue; 4 | import io.vertx.blueprint.kue.queue.Job; 5 | import io.vertx.blueprint.kue.queue.Priority; 6 | import io.vertx.core.AbstractVerticle; 7 | import io.vertx.core.json.JsonObject; 8 | 9 | /** 10 | * Vert.x Blueprint - Job Queue 11 | * Example - A delayed email processing verticle 12 | * This example shows the delayed jobs 13 | * 14 | * @author Eric Zhao 15 | */ 16 | public class DelayedEmailVerticle extends AbstractVerticle { 17 | 18 | @Override 19 | public void start() throws Exception { 20 | // create our job queue 21 | Kue kue = Kue.createQueue(vertx, config()); 22 | 23 | JsonObject data = new JsonObject() 24 | .put("title", "Account renewal required") 25 | .put("to", "qinxin@jianpo.xyz") 26 | .put("template", "renewal-email"); 27 | 28 | // create a delayed email job 29 | Job email = kue.createJob("email", data) 30 | .setDelay(8888) 31 | .priority(Priority.HIGH) 32 | .onComplete(j -> System.out.println("renewal job completed")); 33 | 34 | kue.createJob("email", new JsonObject().put("title", "Account expired") 35 | .put("to", "qinxin@jianpo.xyz") 36 | .put("template", "expired-email")) 37 | .setDelay(26666) 38 | .priority(Priority.HIGH) 39 | .save() // save job 40 | .compose(c -> email.save()) // save another job 41 | .setHandler(sr -> { 42 | if (sr.succeeded()) { 43 | // process emails 44 | kue.processBlocking("email", 10, job -> { 45 | vertx.setTimer(2016, l -> job.done()); // cost 2s to process 46 | }); 47 | } else { 48 | sr.cause().printStackTrace(); 49 | } 50 | }); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/javascripts/utils.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * kue - utils 3 | * Copyright (c) 2010 LearnBoost 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Format `ms` in words. 9 | * 10 | * @param {Number} ms 11 | * @return {String} 12 | */ 13 | 14 | function relative(ms) { 15 | var sec = 1000 16 | , min = 60 * sec 17 | , hour = 60 * min; 18 | 19 | function n(n, name) { 20 | n = Math.round(n); 21 | return n + ' ' + name + (n > 1 ? 's' : ''); 22 | } 23 | 24 | if (isNaN(ms)) return ''; 25 | if (ms < sec) return 'less than one second'; 26 | if (ms < min) return n(ms / sec, 'second'); 27 | if (ms < hour) return n(ms / min, 'minute'); 28 | return n(ms / hour, 'hour'); 29 | // TBD: larger than an hour or so, we should 30 | // have some nice date formatting 31 | } 32 | 33 | /** 34 | * Default job states. 35 | */ 36 | 37 | var states = { 38 | active: 'active', inactive: 'inactive', failed: 'failed', complete: 'complete', delayed: 'delayed' 39 | }; 40 | 41 | /** 42 | * Default job priority map. 43 | */ 44 | 45 | var priorities = { 46 | '10': 'low', '0': 'normal', '-5': 'medium', '-10': 'high', '-15': 'critical' 47 | }; 48 | 49 | /** 50 | * Return priority string for `job`. 51 | * 52 | * @param {Job} job 53 | * @return {String} 54 | */ 55 | 56 | function priority(job) { 57 | return priorities[job.priority] || job.priority; 58 | } 59 | 60 | /** 61 | * Generate options from `obj`. 62 | * 63 | * @param {Object} obj 64 | * @param {String} selected 65 | * @return {String} 66 | */ 67 | 68 | function options(obj, selected) { 69 | var html = ''; 70 | for (var key in obj) { 71 | html += '\n'; 74 | } 75 | return html; 76 | } 77 | -------------------------------------------------------------------------------- /kue-example/src/main/java/io/vertx/blueprint/kue/example/ManyJobsProcessingVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.example; 2 | 3 | import io.vertx.blueprint.kue.Kue; 4 | import io.vertx.blueprint.kue.queue.Job; 5 | import io.vertx.blueprint.kue.queue.Priority; 6 | import io.vertx.core.AbstractVerticle; 7 | import io.vertx.core.Future; 8 | import io.vertx.core.json.JsonObject; 9 | 10 | /** 11 | * Vert.x Blueprint - Job Queue 12 | * Example - illustrates many jobs generating and processing 13 | * 14 | * @author Eric Zhao 15 | */ 16 | public class ManyJobsProcessingVerticle extends AbstractVerticle { 17 | 18 | private Kue kue; 19 | 20 | @Override 21 | public void start() throws Exception { 22 | // create our job queue 23 | kue = Kue.createQueue(vertx, config()); 24 | 25 | create().setHandler(r0 -> { 26 | if (r0.succeeded()) { 27 | // process logic start (6 at a time) 28 | kue.process("video conversion", 6, job -> { 29 | int frames = job.getData().getInteger("frames", 100); 30 | next(0, frames, job); 31 | }); 32 | // process logic end 33 | } else { 34 | r0.cause().printStackTrace(); 35 | } 36 | }); 37 | } 38 | 39 | private Future create() { 40 | JsonObject data = new JsonObject() 41 | .put("title", "converting video to avi") 42 | .put("user", 1) 43 | .put("frames", 200); 44 | 45 | // create an video conversion job 46 | Job job = kue.createJob("video conversion", data) 47 | .priority(Priority.NORMAL); 48 | vertx.setTimer(2000, l -> create()); // may cause issue 49 | return job.save(); 50 | } 51 | 52 | // pretend we are doing some work 53 | private void next(int i, int frames, Job job) { 54 | vertx.setTimer(26, r -> { 55 | job.progress(i, frames); 56 | if (i == frames) 57 | job.done(); 58 | else 59 | next(i + 1, frames, job); 60 | }); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /docs/en/doc-core.toc.md: -------------------------------------------------------------------------------- 1 | - [Preface](#preface) 2 | - [Message system in Vert.x](#message-system-in-vert-x) 3 | - [Publish/subscribe messaging](#publish-subscribe-messaging) 4 | - [Point to point messaging](#point-to-point-messaging) 5 | - [Request-response messaging](#request-response-messaging) 6 | - [Basic design of Vert.x Kue](#basic-design-of-vert-x-kue) 7 | - [Components of Vert.x Kue](#components-of-vert-x-kue) 8 | - [Vert.x Kue Core](#vert-x-kue-core) 9 | - [Future based asynchronous pattern](#future-based-asynchronous-pattern) 10 | - [Events in Vert.x Kue](#events-in-vert-x-kue) 11 | - [Job state](#job-state) 12 | - [Workflow diagram](#workflow-diagram) 13 | - [Project structure](#project-structure) 14 | - [Job entity - not only a data object](#job-entity-not-only-a-data-object) 15 | - [Job properties](#job-properties) 16 | - [Job event helper methods](#job-event-helper-methods) 17 | - [Store format in Redis](#store-format-in-redis) 18 | - [Change job state](#change-job-state) 19 | - [Save job](#save-job) 20 | - [Remove job](#remove-job) 21 | - [Job events listener](#job-events-listener) 22 | - [Update progress](#update-progress) 23 | - [Job failure, error and attempt](#job-failure-error-and-attempt) 24 | - [Event bus service - JobService](#event-bus-service-jobservice) 25 | - [Async RPC](#async-rpc) 26 | - [Async service interface](#async-service-interface) 27 | - [Job service implementation](#job-service-implementation) 28 | - [Register the job service](#register-the-job-service) 29 | - [Kue - The job queue](#kue-the-job-queue) 30 | - [Future-based encapsulation](#future-based-encapsulation) 31 | - [Process and processBlocking](#process-and-processblocking) 32 | - [Check job promotion](#check-job-promotion) 33 | - [Here we process jobs - KueWorker](#here-we-process-jobs-kueworker) 34 | - [Prepare and start](#prepare-and-start) 35 | - [Get appropriate job with zpop command](#get-appropriate-job-with-zpop-command) 36 | - [The true "process"](#the-true-process-) 37 | - [What if the job fails?](#what-if-the-job-fails-) 38 | - [CallbackKue - Polyglot support](#callbackkue-polyglot-support) 39 | - [Show time!](#show-time-) 40 | - [Finish!](#finish-) 41 | -------------------------------------------------------------------------------- /kue-example/src/main/java/io/vertx/blueprint/kue/example/LearningVertxVerticle.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.example; 2 | 3 | import io.vertx.blueprint.kue.Kue; 4 | import io.vertx.blueprint.kue.queue.Job; 5 | import io.vertx.blueprint.kue.queue.Priority; 6 | import io.vertx.core.AbstractVerticle; 7 | import io.vertx.core.json.JsonObject; 8 | 9 | /** 10 | * Vert.x Blueprint - Job Queue 11 | * Example - A learning Vert.x process verticle! 12 | * This example shows the job events 13 | * 14 | * @author Eric Zhao 15 | */ 16 | public class LearningVertxVerticle extends AbstractVerticle { 17 | 18 | @Override 19 | public void start() throws Exception { 20 | // create our job queue 21 | Kue kue = Kue.createQueue(vertx, config()); 22 | 23 | // consume queue error 24 | kue.on("error", message -> 25 | System.out.println("[Global Error] " + message.body())); 26 | 27 | JsonObject data = new JsonObject() 28 | .put("title", "Learning Vert.x") 29 | .put("content", "core"); 30 | 31 | // we are going to learn Vert.x, so create a job! 32 | Job j = kue.createJob("learn vertx", data) 33 | .priority(Priority.HIGH) 34 | .onComplete(r -> { // on complete handler 35 | System.out.println("Feeling: " + r.getResult().getString("feeling", "none")); 36 | }).onFailure(r -> { // on failure handler 37 | System.out.println("eee...so difficult..."); 38 | }).onProgress(r -> { // on progress modifying handler 39 | System.out.println("I love this! My progress => " + r); 40 | }); 41 | 42 | // save the job 43 | j.save().setHandler(r0 -> { 44 | if (r0.succeeded()) { 45 | // start learning! 46 | kue.processBlocking("learn vertx", 1, job -> { 47 | job.progress(10, 100); 48 | // aha...spend 3 seconds to learn! 49 | vertx.setTimer(3000, r1 -> { 50 | job.setResult(new JsonObject().put("feeling", "amazing and wonderful!")) // set a result to the job 51 | .done(); // finish learning! 52 | }); 53 | }); 54 | } else { 55 | System.err.println("Wow, something happened: " + r0.cause().getMessage()); 56 | } 57 | }); 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Vert.x Kue 2 | 3 | [![Build Status](https://travis-ci.org/sczyh30/vertx-kue.svg?branch=master)](https://travis-ci.org/sczyh30/vertx-kue) 4 | 5 | **Vert.x Kue** is a priority task queue developed with Vert.x and backed by **Redis**. 6 | It's a Vert.x implementation version of [Automattic/kue](https://github.com/Automattic/kue). 7 | 8 | This blueprint is an introduction to **message-based application development using Vert.x**. 9 | 10 | ## Detailed Document 11 | 12 | Detailed documents and tutorials: 13 | 14 | - [English Version](http://sczyh30.github.io/vertx-kue/kue-core/index.html) 15 | - [中文文档](http://sczyh30.github.io/vertx-kue/cn/kue-core/index.html) 16 | 17 | ## Features 18 | 19 | - Job priority 20 | - Delayed jobs 21 | - Process many jobs simultaneously 22 | - Job and queue event 23 | - Optional retries with backoff 24 | - RESTful JSON API 25 | - Rich integrated UI (with the help of Automattic/kue's UI) 26 | - UI progress indication 27 | - Job specific logging 28 | - Future-based asynchronous model 29 | - Polyglot language support 30 | - Powered by Vert.x! 31 | 32 | For the detail of the features, please see [Vert.x Kue Features](docs/en/vertx-kue-features-en.md). 33 | 34 | ## Build/Run 35 | 36 | First build the code: 37 | 38 | ``` 39 | gradle build -x test 40 | ``` 41 | 42 | ### Run in local 43 | 44 | Vert.x Kue requires Redis running: 45 | 46 | ``` 47 | redis-server 48 | ``` 49 | 50 | Then we can run the example: 51 | 52 | ``` 53 | java -jar kue-core/build/libs/vertx-blueprint-kue-core.jar -cluster 54 | java -jar kue-http/build/libs/vertx-blueprint-kue-http.jar -cluster 55 | java -jar kue-example/build/libs/vertx-blueprint-kue-example.jar -cluster 56 | ``` 57 | 58 | Then you can visit `http://localhost:8080` to inspect the queue via Kue UI in the browser. 59 | 60 | ![](docs/images/vertx_kue_ui_1.png) 61 | 62 | ### Run with Docker Compose 63 | 64 | To run Vert.x Kue with Docker Compose: 65 | 66 | ``` 67 | docker-compose up --build 68 | ``` 69 | 70 | Then you can run your applications in the terminal. For example: 71 | 72 | ``` 73 | java -jar kue-example/build/libs/vertx-blueprint-kue-example.jar -cluster 74 | ``` 75 | 76 | # Architecture 77 | 78 | ![Diagram - How Vert.x Kue works](https://raw.githubusercontent.com/sczyh30/vertx-kue/master/docs/images/kue_diagram.png) 79 | 80 | ## Want to improve this blueprint ? 81 | 82 | Forks and PRs are definitely welcome ! 83 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/javascripts/loading.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * kue - LoadingIndicator 3 | * Copyright (c) 2011 LearnBoost 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Initialize a new `LoadingIndicator`. 9 | */ 10 | 11 | function LoadingIndicator() { 12 | this.size(0); 13 | this.fontSize(9); 14 | this.font('helvetica, arial, sans-serif'); 15 | } 16 | 17 | /** 18 | * Set size to `n`. 19 | * 20 | * @param {Number} n 21 | * @return {LoadingIndicator} for chaining 22 | * @api public 23 | */ 24 | 25 | LoadingIndicator.prototype.size = function (n) { 26 | this._size = n; 27 | return this; 28 | }; 29 | 30 | /** 31 | * Set font size to `n`. 32 | * 33 | * @param {Number} n 34 | * @return {LoadingIndicator} for chaining 35 | * @api public 36 | */ 37 | 38 | LoadingIndicator.prototype.fontSize = function (n) { 39 | this._fontSize = n; 40 | return this; 41 | }; 42 | 43 | /** 44 | * Set font `family`. 45 | * 46 | * @param {String} family 47 | * @return {LoadingIndicator} for chaining 48 | */ 49 | 50 | LoadingIndicator.prototype.font = function (family) { 51 | this._font = family; 52 | return this; 53 | }; 54 | 55 | /** 56 | * Update pos to `n`. 57 | * 58 | * @param {Number} n 59 | * @return {LoadingIndicator} for chaining 60 | */ 61 | 62 | LoadingIndicator.prototype.update = function (n) { 63 | this.pos = n; 64 | return this; 65 | }; 66 | 67 | /** 68 | * Draw on `ctx`. 69 | * 70 | * @param {CanvasRenderingContext2d} ctx 71 | * @return {LoadingIndicator} for chaining 72 | */ 73 | 74 | LoadingIndicator.prototype.draw = function (ctx) { 75 | var pos = this.pos % 360 76 | , size = this._size 77 | , half = size / 2 78 | , x = half 79 | , y = half 80 | , rad = half - 1 81 | , fontSize = this._fontSize; 82 | 83 | ctx.font = fontSize + 'px ' + this._font; 84 | 85 | ctx.clearRect(0, 0, size, size); 86 | 87 | // outer circle 88 | ctx.strokeStyle = '#9f9f9f'; 89 | ctx.beginPath(); 90 | ctx.arc(x, y, rad, pos, Math.PI / 2 + pos, false); 91 | ctx.stroke(); 92 | 93 | // inner circle 94 | ctx.strokeStyle = '#eee'; 95 | ctx.beginPath(); 96 | ctx.arc(x, y, rad - 3, -pos, Math.PI / 2 - pos, false); 97 | ctx.stroke(); 98 | 99 | // text 100 | var text = 'Loading' 101 | , w = ctx.measureText(text).width; 102 | 103 | ctx.fillText( 104 | text 105 | , x - w / 2 + 1 106 | , y + fontSize / 2 - 1); 107 | 108 | return this; 109 | }; 110 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/util/RedisHelper.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.util; 2 | 3 | import io.vertx.blueprint.kue.queue.JobState; 4 | import io.vertx.core.Vertx; 5 | import io.vertx.core.json.JsonObject; 6 | import io.vertx.redis.RedisClient; 7 | import io.vertx.redis.RedisOptions; 8 | 9 | /** 10 | * Helper class for operating Redis. 11 | * 12 | * @author Eric Zhao 13 | */ 14 | public final class RedisHelper { 15 | 16 | private static final String VERTX_KUE_REDIS_PREFIX = "vertx_kue"; 17 | 18 | private RedisHelper() { 19 | } 20 | 21 | /** 22 | * Factory method for creating a Redis client in Vert.x context. 23 | * 24 | * @param vertx Vertx instance 25 | * @param config configuration 26 | * @return the new Redis client instance 27 | */ 28 | public static RedisClient client(Vertx vertx, JsonObject config) { 29 | return RedisClient.create(vertx, options(config)); 30 | } 31 | 32 | /** 33 | * Factory method for creating a default local Redis client configuration. 34 | * 35 | * @param config configuration from Vert.x context 36 | * @return the new configuration instance 37 | */ 38 | public static RedisOptions options(JsonObject config) { 39 | return new RedisOptions() 40 | .setHost(config.getString("redis.host", "127.0.0.1")) 41 | .setPort(config.getInteger("redis.port", 6379)); 42 | } 43 | 44 | /** 45 | * Wrap the key with prefix of Vert.x Kue namespace. 46 | * 47 | * @param key the key to wrap 48 | * @return the wrapped key 49 | */ 50 | public static String getKey(String key) { 51 | return VERTX_KUE_REDIS_PREFIX + ":" + key; 52 | } 53 | 54 | /** 55 | * Generate the key of a certain task state with prefix of Vert.x Kue namespace. 56 | * 57 | * @param state task state 58 | * @return the generated key 59 | */ 60 | public static String getStateKey(JobState state) { 61 | return VERTX_KUE_REDIS_PREFIX + ":jobs:" + state.name(); 62 | } 63 | 64 | /** 65 | * Create an id for the zset to preserve FIFO order. 66 | * 67 | * @param id id 68 | */ 69 | public static String createFIFO(long id) { 70 | String idLen = "" + ("" + id).length(); 71 | int len = 2 - idLen.length(); 72 | while (len-- > 0) 73 | idLen = "0" + idLen; 74 | return idLen + "|" + id; 75 | } 76 | 77 | /** 78 | * Parse out original ID from zid. 79 | * 80 | * @param zid zid 81 | */ 82 | public static String stripFIFO(String zid) { 83 | return zid.substring(zid.indexOf('|') + 1); 84 | } 85 | 86 | /** 87 | * Parse out original ID from zid. 88 | * 89 | * @param zid zid 90 | */ 91 | public static long numStripFIFO(String zid) { 92 | return Long.parseLong(zid.substring(zid.indexOf('|') + 1)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/javascripts/progress.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * kue - Progress 3 | * Copyright (c) 2011 LearnBoost 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Initialize a new `Progress` indicator. 9 | */ 10 | 11 | function Progress() { 12 | this.percent = 0; 13 | this.size(0); 14 | this.fontSize(12); 15 | this.font('helvetica, arial, sans-serif'); 16 | } 17 | 18 | /** 19 | * Set progress size to `n`. 20 | * 21 | * @param {Number} n 22 | * @return {Progress} for chaining 23 | * @api public 24 | */ 25 | 26 | Progress.prototype.size = function (n) { 27 | this._size = n; 28 | return this; 29 | }; 30 | 31 | /** 32 | * Set text to `str`. 33 | * 34 | * @param {String} str 35 | * @return {Progress} for chaining 36 | * @api public 37 | */ 38 | 39 | Progress.prototype.text = function (str) { 40 | this._text = str; 41 | return this; 42 | }; 43 | 44 | /** 45 | * Set font size to `n`. 46 | * 47 | * @param {Number} n 48 | * @return {Progress} for chaining 49 | * @api public 50 | */ 51 | 52 | Progress.prototype.fontSize = function (n) { 53 | this._fontSize = n; 54 | return this; 55 | }; 56 | 57 | /** 58 | * Set font `family`. 59 | * 60 | * @param {String} family 61 | * @return {Progress} for chaining 62 | */ 63 | 64 | Progress.prototype.font = function (family) { 65 | this._font = family; 66 | return this; 67 | }; 68 | 69 | /** 70 | * Update percentage to `n`. 71 | * 72 | * @param {Number} n 73 | * @return {Progress} for chaining 74 | */ 75 | 76 | Progress.prototype.update = function (n) { 77 | this.percent = n; 78 | return this; 79 | }; 80 | 81 | /** 82 | * Draw on `ctx`. 83 | * 84 | * @param {CanvasRenderingContext2d} ctx 85 | * @return {Progress} for chaining 86 | */ 87 | 88 | Progress.prototype.draw = function (ctx) { 89 | var percent = Math.min(this.percent, 100) 90 | , size = this._size 91 | , half = size / 2 92 | , x = half 93 | , y = half 94 | , rad = half - 1 95 | , fontSize = this._fontSize; 96 | 97 | ctx.font = fontSize + 'px ' + this._font; 98 | 99 | var angle = Math.PI * 2 * (percent / 100); 100 | ctx.clearRect(0, 0, size, size); 101 | 102 | // outer circle 103 | ctx.strokeStyle = '#9f9f9f'; 104 | ctx.beginPath(); 105 | ctx.arc(x, y, rad, 0, angle, false); 106 | ctx.stroke(); 107 | 108 | // inner circle 109 | ctx.strokeStyle = '#eee'; 110 | ctx.beginPath(); 111 | ctx.arc(x, y, rad - 1, 0, angle, true); 112 | ctx.stroke(); 113 | 114 | // text 115 | var text = this._text || (percent | 0) + '%' 116 | , w = ctx.measureText(text).width; 117 | 118 | ctx.fillText( 119 | text 120 | , x - w / 2 + 1 121 | , y + fontSize / 2 - 1); 122 | 123 | return this; 124 | }; 125 | -------------------------------------------------------------------------------- /docs/zh-cn/doc-http.zh-cn.md: -------------------------------------------------------------------------------- 1 | 在这部分教程中,我们将粗略地探索一下`kue-http`模块的实现。 2 | 3 | # Vert.x Kue REST API 4 | 5 | `kue-http`模块中只有一个类 `KueHttpVerticle`,作为整个REST API以及UI服务的实现。对REST API部分来说,如果看过我们之前的 [Vert.x 蓝图 | 待办事项服务开发教程](http://sczyh30.github.io/vertx-blueprint-todo-backend/cn/) 的话,你应该对这一部分非常熟悉了,因此这里我们就不详细解释了。有关使用Vert.x Web实现REST API的教程可参考 [Vert.x 蓝图 | 待办事项服务开发教程](http://sczyh30.github.io/vertx-blueprint-todo-backend/cn/)。 6 | 7 | # 将Kue UI与Vert.x Web进行适配 8 | 9 | 除了REST API之外,我们还给Vert.x Kue提供了一个用户界面。我们复用了Automattic/Kue的用户界面所以我们就不用写前端代码了(部分API有变动的地方我已进行了修改)。我们只需要将前端代码与Vert.x Web适配即可。 10 | 11 | 首先,前端的代码都属于静态资源,因此我们需要配置路由来允许访问静态资源: 12 | 13 | ```java 14 | router.route().handler(StaticHandler.create(root)); 15 | ``` 16 | 17 | 这样我们就可以直接访问静态资源咯~ 18 | 19 | 注意到Kue UI使用了**Jade**(最近貌似改名叫Pug了)作为模板引擎,因此我们需要一个Jade模板解析器。好在Vert.x Web提供了一个Jade模板解析的实现: `io.vertx:vertx-web-templ-jade`,所以我们可以利用这个实现来渲染UI。首先在类中定义一个`JadeTemplateEngine`并在`start`方法中初始化: 20 | 21 | ```java 22 | engine = JadeTemplateEngine.create(); 23 | ``` 24 | 25 | 然后我们就可以写一个处理器方法来根据不同的任务状态来渲染UI: 26 | 27 | ```java 28 | private void render(RoutingContext context, String state) { 29 | final String uiPath = "webroot/views/job/list.jade"; // (1) 30 | String title = config().getString("kue.ui.title", "Vert.x Kue"); 31 | kue.getAllTypes() 32 | .setHandler(resultHandler(context, r -> { 33 | context.put("state", state) // (2) 34 | .put("types", r) 35 | .put("title", title); 36 | engine.render(context, uiPath, res -> { // (3) 37 | if (res.succeeded()) { 38 | context.response() 39 | .putHeader("content-type", "text/html") // (4) 40 | .end(res.result()); 41 | } else { 42 | context.fail(res.cause()); 43 | } 44 | }); 45 | })); 46 | } 47 | ``` 48 | 49 | 首先我们需要给渲染引擎指定我们前端代码的地址 (1)。然后我们从Redis中获取其中所有的任务类型,然后向解析器context中添加任务状态、网页标题、任务类型等信息供渲染器渲染使用 (2)。接着我们就可以调用`engine.render(context, path, handler)`方法进行渲染 (3)。如果渲染成功,我们将页面写入HTTP Response (4)。 50 | 51 | 现在我们可以利用`render`方法去实现其它的路由函数了: 52 | 53 | ```java 54 | private void handleUIActive(RoutingContext context) { 55 | render(context, "active"); 56 | } 57 | ``` 58 | 59 | 然后我们给它绑个路由就可以了: 60 | 61 | ```java 62 | router.route(KUE_UI_ACTIVE).handler(this::handleUIActive); 63 | ``` 64 | 65 | 是不是非常方便呢?不仅如此,Vert.x Web还提供了其它各种模板引擎的支持,比如 *FreeMaker*, *Pebble* 以及 *Thymeleaf 3*。如果感兴趣的话,你可以查阅[官方文档](http://vertx.io/docs/vertx-web/java/#_templates)来获取详细的使用指南。 66 | 67 | # 展示时间! 68 | 69 | 是不是等不及要看UI长啥样了?现在我们就来展示一下!首先构建项目: 70 | 71 | gradle build 72 | 73 | `kue-http`需要`kue-core`运行着(因为`kue-core`里注册了Event Bus服务),因此我们先运行`kue-core`,再运行`kue-http`。不要忘记运行Redis: 74 | 75 | redis-server 76 | java -jar kue-core/build/libs/vertx-blueprint-kue-core.jar -cluster -ha -conf config/config.json 77 | java -jar kue-http/build/libs/vertx-blueprint-kue-http.jar -cluster -ha -conf config/config.json 78 | 79 | 为了更好地观察任务处理的流程,我们再运行一个示例: 80 | 81 | java -jar kue-example/build/libs/vertx-blueprint-kue-example.jar -cluster -ha -conf config/config.json 82 | 83 | 好啦!现在在浏览器中访问`http://localhost:8080`,我们的Kue UI就呈现在我们眼前啦! 84 | 85 | ![](https://raw.githubusercontent.com/sczyh30/vertx-blueprint-job-queue/master/docs/images/vertx_kue_ui_1.png) 86 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/stylesheets/job.styl: -------------------------------------------------------------------------------- 1 | @import 'mixins' 2 | 3 | #job-template 4 | display: none 5 | 6 | bar(color) 7 | background: linear-gradient(top, color + 20%, color) 8 | border: 1px solid rgba(white, .2) 9 | color: white 10 | 11 | // generic blocks 12 | 13 | .block 14 | decorated-box() 15 | width: 90% 16 | margin: 10px 25px 17 | padding: 20px 25px 18 | h2 19 | margin: 0 20 | absolute: top 5px left -15px 21 | padding: 5px 22 | font-size: 10px 23 | border-radius: left 5px right 2px 24 | background: linear-gradient(left, menu-fg - 10%, 50% menu-fg + 5%) 25 | box-shadow: -1px 0 1px 1px rgba(black, .1) 26 | color: white 27 | text-shadow: 1px 1px 1px #444 28 | .type 29 | color: lighter + 20% 30 | 31 | // job delay 32 | .job td.title em 33 | color: lighter + 20% 34 | 35 | // job blocks 36 | 37 | .job .block 38 | position: relative 39 | background: job-bg 40 | cursor: pointer 41 | table td:first-child 42 | display: none 43 | .progress 44 | absolute: top 15px right 20px 45 | .attempts 46 | display: none 47 | absolute: top right 48 | padding: 5px 8px 49 | border-radius: 2px 50 | font-size: 10px 51 | .remove 52 | absolute: top 30px right -6px 53 | /*background: white*/ 54 | background: #F05151 55 | color: white 56 | display: block 57 | width: size = 20px 58 | height: size 59 | line-height: size 60 | text-align: center 61 | font-size: 12px 62 | font-weight: bold 63 | outline: none 64 | border: 1px solid #eee 65 | border-radius: size 66 | transition: opacity 200ms, top 300ms 67 | opacity: 0 68 | &:hover 69 | border: 1px solid #eee - 10% 70 | &:active 71 | border: 1px solid #eee - 20% 72 | .restart 73 | absolute: top 30px right -6px 74 | /*background: white*/ 75 | background: #00e600 76 | color: white 77 | display: block 78 | width: size = 20px 79 | height: size 80 | line-height: size 81 | text-align: center 82 | font-size: 12px 83 | font-weight: bold 84 | outline: none 85 | border: 1px solid #eee 86 | border-radius: size 87 | transition: opacity 200ms, top 300ms 88 | opacity: 0 89 | &:hover 90 | border: 1px solid #eee - 10% 91 | &:active 92 | border: 1px solid #eee - 20% 93 | &:hover 94 | .remove 95 | opacity: 1 96 | top: -6px 97 | .restart 98 | opacity: 1 99 | top: 16px 100 | 101 | // details 102 | 103 | .job .details 104 | background: dark 105 | width: 89% 106 | margin-top: -10px 107 | margin-left: 35px 108 | border-radius: bottom 5px 109 | box-shadow: inset 0 1px 10px 0 rgba(black, .8) 110 | transition: padding 200ms, height 200ms 111 | height: 0 112 | overflow: hidden 113 | table 114 | width: 100% 115 | td:first-child 116 | width: 60px 117 | color: light + 30% 118 | &.show 119 | padding: 15px 20px 120 | height: auto 121 | 122 | // job log 123 | 124 | .job ul.log 125 | reset-list() 126 | margin: 5px 127 | padding: 10px 128 | max-height: 100px 129 | overflow-y: auto 130 | border-radius: 5px 131 | width: 95% 132 | li 133 | padding: 5px 0 134 | border-bottom: 1px dotted light - 35% 135 | color: light 136 | &:last-child 137 | border-bottom: none 138 | 139 | // scrollbar 140 | 141 | .job .details 142 | ::-webkit-scrollbar 143 | width: 2px 144 | ::-webkit-scrollbar-thumb:vertical 145 | background: light + 20% 146 | ::-webkit-scrollbar-track 147 | border: 1px solid rgba(white, .1) 148 | 149 | // sections 150 | 151 | .job .details > div 152 | padding: 10px 0 153 | border-bottom: 1px solid light - 35% 154 | &:last-child 155 | border-bottom: none 156 | -------------------------------------------------------------------------------- /docs/en/doc-http.md: -------------------------------------------------------------------------------- 1 | In this tutorial we are going to take a quick look on implementation of `kue-http`. 2 | 3 | # Vert.x Kue REST API 4 | 5 | Our `kue-http` component only have one class `KueHttpVerticle`, where both REST API and UI are implemented. For REST API, the approach is quite similar to what we have elaborated in [Vert.x Blueprint - Todo-Backend Tutorial](http://sczyh30.github.io/vertx-blueprint-todo-backend/) so here we don't explain the code. You can refer to [Vert.x Blueprint - Todo-Backend Tutorial](http://sczyh30.github.io/vertx-blueprint-todo-backend/) for tutorial about **Vert.x Web**. 6 | 7 | # Adapt Kue UI with Vert.x Web 8 | 9 | Besides the REST API, there is also a user interface in Vert.x Kue. We reused the frontend code of Automattic/Kue's UI so here we should adapt the UI with Vert.x Web. 10 | 11 | Frontend materials are static resources so first we need to config the static resources route using `router.route().handler(StaticHandler.create(root))`. Then we can visit static resources directly from the browser. 12 | 13 | Notice that Kue UI uses **Jade**(Now renamed as **Pug**) as ui template so we need a Jade engine. Fortunately, Vert.x Web provides an implementation of Jade engine: `io.vertx:vertx-web-templ-jade` so we can make use of it. First we need to create a `JadeTemplateEngine` in class scope: 14 | 15 | ```java 16 | engine = JadeTemplateEngine.create(); 17 | ``` 18 | 19 | Then we write a method that renders ui for certain job state: 20 | 21 | ```java 22 | private void render(RoutingContext context, String state) { 23 | final String uiPath = "webroot/views/job/list.jade"; // (1) 24 | String title = config().getString("kue.ui.title", "Vert.x Kue"); 25 | kue.getAllTypes() 26 | .setHandler(resultHandler(context, r -> { 27 | context.put("state", state) // (2) 28 | .put("types", r) 29 | .put("title", title); 30 | engine.render(context, uiPath, res -> { // (3) 31 | if (res.succeeded()) { 32 | context.response() 33 | .putHeader("content-type", "text/html") // (4) 34 | .end(res.result()); 35 | } else { 36 | context.fail(res.cause()); 37 | } 38 | }); 39 | })); 40 | } 41 | ``` 42 | 43 | We need to specify the frontend resource path first (1). Then we get all existing job types and put `state`, `types` and `title` into context (2), which will be used when rendering pages. Next we can call `engine.render(context, path, handler)` to render the pages (3). In the handler, if successfully rendered, we send response with `end` method (4). 44 | 45 | Now we can use this `render` handler method: 46 | 47 | ```java 48 | private void handleUIActive(RoutingContext context) { 49 | render(context, "active"); 50 | } 51 | ``` 52 | 53 | And bind a route for it: 54 | 55 | ```java 56 | router.route(KUE_UI_ACTIVE).handler(this::handleUIActive); 57 | ``` 58 | 59 | Very convenient, isn't it? Vert.x Web also supports various other kinds of template engine such as *FreeMaker*, *Pebble* and *Thymeleaf 3*. If you are interested in it, you can refer to [the documentation](http://vertx.io/docs/vertx-web/java/#_templates). 60 | 61 | # Show time! 62 | 63 | Can't wait to see the UI? Now let's demonstrate it! First build the project: 64 | 65 | gradle build 66 | 67 | `kue-http` requires `kue-core` starting, so first deploy the `KueVerticle`. Don't forget to start Redis first: 68 | 69 | redis-server 70 | java -jar kue-core/build/libs/vertx-blueprint-kue-core.jar -cluster -ha -conf config/config.json 71 | java -jar kue-http/build/libs/vertx-blueprint-kue-http.jar -cluster -ha -conf config/config.json 72 | 73 | To watch the effects of the job processing, we can also run an example: 74 | 75 | java -jar kue-example/build/libs/vertx-blueprint-kue-example.jar -cluster -ha -conf config/config.json 76 | 77 | Then visit `http://localhost:8080` and we can inspect the queue very clearly in the browser: 78 | 79 | ![](https://raw.githubusercontent.com/sczyh30/vertx-blueprint-job-queue/master/docs/images/vertx_kue_ui_1.png) 80 | -------------------------------------------------------------------------------- /kue-core/src/main/generated/io/vertx/blueprint/kue/rxjava/CallbackKue.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.blueprint.kue.rxjava; 18 | 19 | import java.util.Map; 20 | import rx.Observable; 21 | import rx.Single; 22 | import io.vertx.blueprint.kue.service.rxjava.JobService; 23 | import io.vertx.blueprint.kue.queue.JobState; 24 | import io.vertx.rxjava.core.Vertx; 25 | import io.vertx.rxjava.core.eventbus.Message; 26 | import io.vertx.core.json.JsonArray; 27 | 28 | import java.util.List; 29 | 30 | import io.vertx.blueprint.kue.queue.Job; 31 | import io.vertx.core.json.JsonObject; 32 | import io.vertx.core.AsyncResult; 33 | import io.vertx.core.Handler; 34 | 35 | /** 36 | * A callback-based {@link io.vertx.blueprint.kue.rxjava.Kue} interface for Vert.x Codegen to support polyglot languages. 37 | * 38 | *

39 | * NOTE: This class has been automatically generated from the {@link io.vertx.blueprint.kue.CallbackKue original} non RX-ified interface using Vert.x codegen. 40 | */ 41 | 42 | @io.vertx.lang.rxjava.RxGen(io.vertx.blueprint.kue.CallbackKue.class) 43 | public class CallbackKue extends JobService { 44 | 45 | public static final io.vertx.lang.rxjava.TypeArg __TYPE_ARG = new io.vertx.lang.rxjava.TypeArg<>( 46 | obj -> new CallbackKue((io.vertx.blueprint.kue.CallbackKue) obj), 47 | CallbackKue::getDelegate 48 | ); 49 | 50 | private final io.vertx.blueprint.kue.CallbackKue delegate; 51 | 52 | public CallbackKue(io.vertx.blueprint.kue.CallbackKue delegate) { 53 | super(delegate); 54 | this.delegate = delegate; 55 | } 56 | 57 | public io.vertx.blueprint.kue.CallbackKue getDelegate() { 58 | return delegate; 59 | } 60 | 61 | public static CallbackKue createKue(Vertx vertx, JsonObject config) { 62 | CallbackKue ret = CallbackKue.newInstance(io.vertx.blueprint.kue.CallbackKue.createKue(vertx.getDelegate(), config)); 63 | return ret; 64 | } 65 | 66 | public Job createJob(String type, JsonObject data) { 67 | Job ret = delegate.createJob(type, data); 68 | return ret; 69 | } 70 | 71 | public CallbackKue on(String eventType, Handler> handler) { 72 | delegate.on(eventType, new Handler>() { 73 | public void handle(io.vertx.core.eventbus.Message event) { 74 | handler.handle(Message.newInstance(event, io.vertx.lang.rxjava.TypeArg.unknown())); 75 | } 76 | }); 77 | return this; 78 | } 79 | 80 | public CallbackKue saveJob(Job job, Handler> handler) { 81 | delegate.saveJob(job, handler); 82 | return this; 83 | } 84 | 85 | public Single rxSaveJob(Job job) { 86 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 87 | saveJob(job, fut); 88 | })); 89 | } 90 | 91 | public CallbackKue jobProgress(Job job, int complete, int total, Handler> handler) { 92 | delegate.jobProgress(job, complete, total, handler); 93 | return this; 94 | } 95 | 96 | public Single rxJobProgress(Job job, int complete, int total) { 97 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 98 | jobProgress(job, complete, total, fut); 99 | })); 100 | } 101 | 102 | public CallbackKue jobDone(Job job) { 103 | delegate.jobDone(job); 104 | return this; 105 | } 106 | 107 | public CallbackKue jobDoneFail(Job job, Throwable ex) { 108 | delegate.jobDoneFail(job, ex); 109 | return this; 110 | } 111 | 112 | public CallbackKue process(String type, int n, Handler handler) { 113 | delegate.process(type, n, handler); 114 | return this; 115 | } 116 | 117 | public CallbackKue processBlocking(String type, int n, Handler handler) { 118 | delegate.processBlocking(type, n, handler); 119 | return this; 120 | } 121 | 122 | 123 | public static CallbackKue newInstance(io.vertx.blueprint.kue.CallbackKue arg) { 124 | return arg != null ? new CallbackKue(arg) : null; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/CallbackKueImpl.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue; 2 | 3 | import io.vertx.blueprint.kue.queue.Job; 4 | import io.vertx.blueprint.kue.queue.JobState; 5 | import io.vertx.blueprint.kue.service.JobService; 6 | import io.vertx.core.AsyncResult; 7 | import io.vertx.core.Handler; 8 | import io.vertx.core.Vertx; 9 | import io.vertx.core.eventbus.Message; 10 | import io.vertx.core.json.JsonArray; 11 | import io.vertx.core.json.JsonObject; 12 | 13 | import java.util.List; 14 | 15 | 16 | /** 17 | * Implementation of {@link io.vertx.blueprint.kue.CallbackKue}. 18 | * 19 | * @author Eric Zhao 20 | */ 21 | public class CallbackKueImpl implements CallbackKue { 22 | 23 | private final Kue kue; 24 | private final JobService jobService; 25 | 26 | public CallbackKueImpl(Vertx vertx, JsonObject config) { 27 | this.kue = new Kue(vertx, config); 28 | this.jobService = kue.getJobService(); 29 | } 30 | 31 | @Override 32 | public Job createJob(String type, JsonObject data) { 33 | return kue.createJob(type, data); 34 | } 35 | 36 | @Override 37 | public CallbackKue saveJob(Job job, Handler> handler) { 38 | job.save().setHandler(handler); 39 | return this; 40 | } 41 | 42 | @Override 43 | public CallbackKue jobProgress(Job job, int complete, int total, Handler> handler) { 44 | job.progress(complete, total).setHandler(handler); 45 | return this; 46 | } 47 | 48 | @Override 49 | public CallbackKue jobDoneFail(Job job, Throwable ex) { 50 | job.done(ex); 51 | return this; 52 | } 53 | 54 | @Override 55 | public CallbackKue jobDone(Job job) { 56 | job.done(); 57 | return this; 58 | } 59 | 60 | @Override 61 | public CallbackKue on(String eventType, Handler> handler) { 62 | kue.on(eventType, handler); 63 | return this; 64 | } 65 | 66 | @Override 67 | public CallbackKue process(String type, int n, Handler handler) { 68 | kue.process(type, n, handler); 69 | return this; 70 | } 71 | 72 | @Override 73 | public CallbackKue processBlocking(String type, int n, Handler handler) { 74 | kue.processBlocking(type, n, handler); 75 | return this; 76 | } 77 | 78 | @Override 79 | public CallbackKue getJob(long id, Handler> handler) { 80 | jobService.getJob(id, handler); 81 | return this; 82 | } 83 | 84 | @Override 85 | public CallbackKue removeJob(long id, Handler> handler) { 86 | jobService.removeJob(id, handler); 87 | return this; 88 | } 89 | 90 | @Override 91 | public CallbackKue existsJob(long id, Handler> handler) { 92 | jobService.existsJob(id, handler); 93 | return this; 94 | } 95 | 96 | @Override 97 | public CallbackKue getJobLog(long id, Handler> handler) { 98 | jobService.getJobLog(id, handler); 99 | return this; 100 | } 101 | 102 | @Override 103 | public CallbackKue jobRangeByState(String state, long from, long to, String order, Handler>> handler) { 104 | jobService.jobRangeByState(state, from, to, order, handler); 105 | return this; 106 | } 107 | 108 | @Override 109 | public JobService jobRangeByType(String type, String state, long from, long to, String order, Handler>> handler) { 110 | jobService.jobRangeByType(type, state, from, to, order, handler); 111 | return this; 112 | } 113 | 114 | @Override 115 | public JobService jobRange(long from, long to, String order, Handler>> handler) { 116 | jobService.jobRange(from, to, order, handler); 117 | return this; 118 | } 119 | 120 | @Override 121 | public CallbackKue cardByType(String type, JobState state, Handler> handler) { 122 | jobService.cardByType(type, state, handler); 123 | return this; 124 | } 125 | 126 | @Override 127 | public CallbackKue card(JobState state, Handler> handler) { 128 | jobService.card(state, handler); 129 | return this; 130 | } 131 | 132 | @Override 133 | public CallbackKue completeCount(String type, Handler> handler) { 134 | jobService.completeCount(type, handler); 135 | return this; 136 | } 137 | 138 | @Override 139 | public CallbackKue failedCount(String type, Handler> handler) { 140 | jobService.failedCount(type, handler); 141 | return this; 142 | } 143 | 144 | @Override 145 | public CallbackKue inactiveCount(String type, Handler> handler) { 146 | jobService.inactiveCount(type, handler); 147 | return this; 148 | } 149 | 150 | @Override 151 | public CallbackKue activeCount(String type, Handler> handler) { 152 | jobService.activeCount(type, handler); 153 | return this; 154 | } 155 | 156 | @Override 157 | public CallbackKue delayedCount(String type, Handler> handler) { 158 | jobService.delayedCount(type, handler); 159 | return this; 160 | } 161 | 162 | @Override 163 | public CallbackKue getAllTypes(Handler>> handler) { 164 | jobService.getAllTypes(handler); 165 | return this; 166 | } 167 | 168 | @Override 169 | public CallbackKue getIdsByState(JobState state, Handler>> handler) { 170 | jobService.getIdsByState(state, handler); 171 | return this; 172 | } 173 | 174 | @Override 175 | public CallbackKue getWorkTime(Handler> handler) { 176 | jobService.getWorkTime(handler); 177 | return this; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /kue-core/src/main/generated/io/vertx/blueprint/kue/queue/JobConverter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.blueprint.kue.queue; 18 | 19 | import io.vertx.core.json.JsonObject; 20 | import io.vertx.core.json.JsonArray; 21 | 22 | /** 23 | * Converter for {@link io.vertx.blueprint.kue.queue.Job}. 24 | * 25 | * NOTE: This class has been automatically generated from the {@link io.vertx.blueprint.kue.queue.Job} original class using Vert.x codegen. 26 | */ 27 | public class JobConverter { 28 | 29 | public static void fromJson(JsonObject json, Job obj) { 30 | if (json.getValue("attempts") instanceof Number) { 31 | obj.setAttempts(((Number) json.getValue("attempts")).intValue()); 32 | } 33 | if (json.getValue("backoff") instanceof JsonObject) { 34 | obj.setBackoff(((JsonObject) json.getValue("backoff")).copy()); 35 | } 36 | if (json.getValue("created_at") instanceof Number) { 37 | obj.setCreated_at(((Number) json.getValue("created_at")).longValue()); 38 | } 39 | if (json.getValue("data") instanceof JsonObject) { 40 | obj.setData(((JsonObject) json.getValue("data")).copy()); 41 | } 42 | if (json.getValue("delay") instanceof Number) { 43 | obj.setDelay(((Number) json.getValue("delay")).longValue()); 44 | } 45 | if (json.getValue("duration") instanceof Number) { 46 | obj.setDuration(((Number) json.getValue("duration")).longValue()); 47 | } 48 | if (json.getValue("failed_at") instanceof Number) { 49 | obj.setFailed_at(((Number) json.getValue("failed_at")).longValue()); 50 | } 51 | if (json.getValue("id") instanceof Number) { 52 | obj.setId(((Number) json.getValue("id")).longValue()); 53 | } 54 | if (json.getValue("max_attempts") instanceof Number) { 55 | obj.setMax_attempts(((Number) json.getValue("max_attempts")).intValue()); 56 | } 57 | if (json.getValue("priority") instanceof String) { 58 | obj.setPriority(io.vertx.blueprint.kue.queue.Priority.valueOf((String) json.getValue("priority"))); 59 | } 60 | if (json.getValue("progress") instanceof Number) { 61 | obj.setProgress(((Number) json.getValue("progress")).intValue()); 62 | } 63 | if (json.getValue("promote_at") instanceof Number) { 64 | obj.setPromote_at(((Number) json.getValue("promote_at")).longValue()); 65 | } 66 | if (json.getValue("removeOnComplete") instanceof Boolean) { 67 | obj.setRemoveOnComplete((Boolean) json.getValue("removeOnComplete")); 68 | } 69 | if (json.getValue("result") instanceof JsonObject) { 70 | obj.setResult(((JsonObject) json.getValue("result")).copy()); 71 | } 72 | if (json.getValue("started_at") instanceof Number) { 73 | obj.setStarted_at(((Number) json.getValue("started_at")).longValue()); 74 | } 75 | if (json.getValue("state") instanceof String) { 76 | obj.setState(io.vertx.blueprint.kue.queue.JobState.valueOf((String) json.getValue("state"))); 77 | } 78 | if (json.getValue("ttl") instanceof Number) { 79 | obj.setTtl(((Number) json.getValue("ttl")).intValue()); 80 | } 81 | if (json.getValue("type") instanceof String) { 82 | obj.setType((String) json.getValue("type")); 83 | } 84 | if (json.getValue("updated_at") instanceof Number) { 85 | obj.setUpdated_at(((Number) json.getValue("updated_at")).longValue()); 86 | } 87 | if (json.getValue("zid") instanceof String) { 88 | obj.setZid((String) json.getValue("zid")); 89 | } 90 | } 91 | 92 | public static void toJson(Job obj, JsonObject json) { 93 | if (obj.getAddress_id() != null) { 94 | json.put("address_id", obj.getAddress_id()); 95 | } 96 | json.put("attempts", obj.getAttempts()); 97 | if (obj.getBackoff() != null) { 98 | json.put("backoff", obj.getBackoff()); 99 | } 100 | json.put("created_at", obj.getCreated_at()); 101 | if (obj.getData() != null) { 102 | json.put("data", obj.getData()); 103 | } 104 | json.put("delay", obj.getDelay()); 105 | json.put("duration", obj.getDuration()); 106 | json.put("failed_at", obj.getFailed_at()); 107 | json.put("id", obj.getId()); 108 | json.put("max_attempts", obj.getMax_attempts()); 109 | if (obj.getPriority() != null) { 110 | json.put("priority", obj.getPriority().name()); 111 | } 112 | json.put("progress", obj.getProgress()); 113 | json.put("promote_at", obj.getPromote_at()); 114 | json.put("removeOnComplete", obj.isRemoveOnComplete()); 115 | if (obj.getResult() != null) { 116 | json.put("result", obj.getResult()); 117 | } 118 | json.put("started_at", obj.getStarted_at()); 119 | if (obj.getState() != null) { 120 | json.put("state", obj.getState().name()); 121 | } 122 | json.put("ttl", obj.getTtl()); 123 | if (obj.getType() != null) { 124 | json.put("type", obj.getType()); 125 | } 126 | json.put("updated_at", obj.getUpdated_at()); 127 | if (obj.getZid() != null) { 128 | json.put("zid", obj.getZid()); 129 | } 130 | } 131 | } -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /kue-http/src/test/java/io/vertx/blueprint/kue/http/KueRestApiTest.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.http; 2 | 3 | import io.vertx.blueprint.kue.Kue; 4 | import io.vertx.blueprint.kue.queue.Job; 5 | import io.vertx.blueprint.kue.queue.KueVerticle; 6 | import io.vertx.core.Vertx; 7 | import io.vertx.core.VertxOptions; 8 | import io.vertx.core.http.HttpClient; 9 | import io.vertx.core.http.HttpMethod; 10 | import io.vertx.core.json.Json; 11 | import io.vertx.core.json.JsonObject; 12 | import io.vertx.ext.unit.Async; 13 | import io.vertx.ext.unit.TestContext; 14 | import io.vertx.ext.unit.junit.VertxUnitRunner; 15 | import org.junit.Before; 16 | import org.junit.BeforeClass; 17 | import org.junit.FixMethodOrder; 18 | import org.junit.Test; 19 | import org.junit.runner.RunWith; 20 | import org.junit.runners.MethodSorters; 21 | 22 | import static org.junit.Assert.*; 23 | 24 | /** 25 | * Vert.x Kue REST API test case 26 | * 27 | * @author Eric Zhao 28 | */ 29 | @FixMethodOrder(MethodSorters.NAME_ASCENDING) 30 | @RunWith(VertxUnitRunner.class) 31 | public class KueRestApiTest { 32 | 33 | private static final int PORT = 8080; 34 | private static final String HOST = "localhost"; 35 | 36 | private static final String TYPE = "test:inserts"; 37 | 38 | private static Kue kue; 39 | 40 | @BeforeClass 41 | public static void setUp(TestContext context) throws Exception { 42 | Async async = context.async(); 43 | Vertx.clusteredVertx(new VertxOptions(), r -> { 44 | if (r.succeeded()) { 45 | Vertx vertx = r.result(); 46 | kue = Kue.createQueue(vertx, new JsonObject()); 47 | vertx.deployVerticle(new KueVerticle(), r2 -> { 48 | if (r2.succeeded()) { 49 | kue.jobRangeByType(TYPE, "inactive", 0, 100, "asc").setHandler(r1 -> { 50 | if (r1.succeeded()) { 51 | r1.result().forEach(Job::remove); 52 | vertx.deployVerticle(new KueHttpVerticle(), r3 -> { 53 | if (r3.succeeded()) 54 | async.complete(); 55 | else 56 | context.fail(r3.cause()); 57 | }); 58 | } else { 59 | context.fail(r1.cause()); 60 | } 61 | }); 62 | } else { 63 | context.fail(r2.cause()); 64 | } 65 | }); 66 | 67 | } else { 68 | context.fail(r.cause()); 69 | } 70 | }); 71 | } 72 | 73 | @Test 74 | public void testApiStats(TestContext context) throws Exception { 75 | Vertx vertx = Vertx.vertx(); 76 | HttpClient client = vertx.createHttpClient(); 77 | Async async = context.async(); 78 | client.getNow(PORT, HOST, "/stats", response -> { 79 | response.bodyHandler(body -> { 80 | JsonObject stats = new JsonObject(body.toString()); 81 | context.assertEquals(stats.getInteger("inactiveCount") > 0, true); 82 | client.close(); 83 | async.complete(); 84 | }); 85 | }); 86 | } 87 | 88 | public void testApiTypeStateStats(TestContext context) throws Exception { 89 | Vertx vertx = Vertx.vertx(); 90 | HttpClient client = vertx.createHttpClient(); 91 | Async async = context.async(); 92 | } 93 | 94 | public void testJobTypes(TestContext context) throws Exception { 95 | Vertx vertx = Vertx.vertx(); 96 | HttpClient client = vertx.createHttpClient(); 97 | Async async = context.async(); 98 | } 99 | 100 | public void testJobRange(TestContext context) throws Exception { 101 | Vertx vertx = Vertx.vertx(); 102 | HttpClient client = vertx.createHttpClient(); 103 | Async async = context.async(); 104 | } 105 | 106 | public void testJobTypeRange(TestContext context) throws Exception { 107 | Vertx vertx = Vertx.vertx(); 108 | HttpClient client = vertx.createHttpClient(); 109 | Async async = context.async(); 110 | } 111 | 112 | public void testJobStateRange(TestContext context) throws Exception { 113 | Vertx vertx = Vertx.vertx(); 114 | HttpClient client = vertx.createHttpClient(); 115 | Async async = context.async(); 116 | } 117 | 118 | @Test 119 | public void testApiGetJob(TestContext context) throws Exception { 120 | Vertx vertx = Vertx.vertx(); 121 | HttpClient client = vertx.createHttpClient(); 122 | Async async = context.async(); 123 | kue.createJob(TYPE, new JsonObject().put("data", TYPE + ":data")) 124 | .save() 125 | .setHandler(jr -> { 126 | if (jr.succeeded()) { 127 | long id = jr.result().getId(); 128 | client.getNow(PORT, HOST, "/job/" + id, response -> response.bodyHandler(body -> { 129 | context.assertEquals(new Job(new JsonObject(body.toString())).getId(), id); 130 | client.close(); 131 | async.complete(); 132 | })); 133 | } else { 134 | context.fail(jr.cause()); 135 | } 136 | }); 137 | } 138 | 139 | @Test 140 | public void testDeleteJob(TestContext context) throws Exception { 141 | Vertx vertx = Vertx.vertx(); 142 | HttpClient client = vertx.createHttpClient(); 143 | Async async = context.async(); 144 | client.delete(PORT, HOST, "/job/66", rsp -> { 145 | context.assertEquals(204, rsp.statusCode()); 146 | client.close(); 147 | async.complete(); 148 | }).end(); 149 | } 150 | 151 | @Test 152 | public void testApiCreateJob(TestContext context) throws Exception { 153 | Vertx vertx = Vertx.vertx(); 154 | HttpClient client = vertx.createHttpClient(); 155 | Async async = context.async(); 156 | Job job = kue.createJob(TYPE, new JsonObject().put("data", TYPE + ":data")); 157 | client.put(PORT, HOST, "/job", response -> { 158 | context.assertEquals(201, response.statusCode()); 159 | response.bodyHandler(body -> { 160 | context.assertEquals(new JsonObject(body.toString()).getString("message"), "job created"); 161 | client.close(); 162 | async.complete(); 163 | }); 164 | }).putHeader("content-type", "application/json") 165 | .end(job.toString()); 166 | } 167 | 168 | } -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/service/JobService.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.service; 2 | 3 | import io.vertx.blueprint.kue.queue.Job; 4 | import io.vertx.blueprint.kue.queue.JobState; 5 | import io.vertx.blueprint.kue.service.impl.JobServiceImpl; 6 | import io.vertx.codegen.annotations.Fluent; 7 | import io.vertx.codegen.annotations.ProxyGen; 8 | import io.vertx.codegen.annotations.VertxGen; 9 | import io.vertx.core.AsyncResult; 10 | import io.vertx.core.Handler; 11 | import io.vertx.core.Vertx; 12 | import io.vertx.core.json.JsonArray; 13 | import io.vertx.core.json.JsonObject; 14 | import io.vertx.serviceproxy.ProxyHelper; 15 | 16 | import java.util.List; 17 | 18 | /** 19 | * Service interface for task operations. 20 | * 21 | * @author Eric Zhao 22 | */ 23 | @ProxyGen 24 | @VertxGen 25 | public interface JobService { 26 | 27 | /** 28 | * Factory method for creating a {@link JobService} instance. 29 | * 30 | * @param vertx Vertx instance 31 | * @param config configuration 32 | * @return the new {@link JobService} instance 33 | */ 34 | static JobService create(Vertx vertx, JsonObject config) { 35 | return new JobServiceImpl(vertx, config); 36 | } 37 | 38 | /** 39 | * Factory method for creating a {@link JobService} service proxy. 40 | * This is useful for doing RPCs. 41 | * 42 | * @param vertx Vertx instance 43 | * @param address event bus address of RPC 44 | * @return the new {@link JobService} service proxy 45 | */ 46 | static JobService createProxy(Vertx vertx, String address) { 47 | return ProxyHelper.createProxy(JobService.class, vertx, address); 48 | } 49 | 50 | /** 51 | * Get the certain from backend by id. 52 | * 53 | * @param id job id 54 | * @param handler async result handler 55 | */ 56 | @Fluent 57 | JobService getJob(long id, Handler> handler); 58 | 59 | /** 60 | * Remove a job by id. 61 | * 62 | * @param id job id 63 | * @param handler async result handler 64 | */ 65 | @Fluent 66 | JobService removeJob(long id, Handler> handler); 67 | 68 | /** 69 | * Judge whether a job with certain id exists. 70 | * 71 | * @param id job id 72 | * @param handler async result handler 73 | */ 74 | @Fluent 75 | JobService existsJob(long id, Handler> handler); 76 | 77 | /** 78 | * Get job log by id. 79 | * 80 | * @param id job id 81 | * @param handler async result handler 82 | */ 83 | @Fluent 84 | JobService getJobLog(long id, Handler> handler); 85 | 86 | /** 87 | * Get a list of job in certain state in range (from, to) with order. 88 | * 89 | * @param state expected job state 90 | * @param from from 91 | * @param to to 92 | * @param order range order 93 | * @param handler async result handler 94 | */ 95 | @Fluent 96 | JobService jobRangeByState(String state, long from, long to, String order, Handler>> handler); 97 | 98 | /** 99 | * Get a list of job in certain state and type in range (from, to) with order. 100 | * 101 | * @param type expected job type 102 | * @param state expected job state 103 | * @param from from 104 | * @param to to 105 | * @param order range order 106 | * @param handler async result handler 107 | */ 108 | @Fluent 109 | JobService jobRangeByType(String type, String state, long from, long to, String order, Handler>> handler); 110 | 111 | /** 112 | * Get a list of job in range (from, to) with order. 113 | * 114 | * @param from from 115 | * @param to to 116 | * @param order range order 117 | * @param handler async result handler 118 | */ 119 | @Fluent 120 | JobService jobRange(long from, long to, String order, Handler>> handler); 121 | 122 | // Runtime cardinality metrics 123 | 124 | /** 125 | * Get cardinality by job type and state. 126 | * 127 | * @param type job type 128 | * @param state job state 129 | * @param handler async result handler 130 | */ 131 | @Fluent 132 | JobService cardByType(String type, JobState state, Handler> handler); 133 | 134 | /** 135 | * Get cardinality by job state. 136 | * 137 | * @param state job state 138 | * @param handler async result handler 139 | */ 140 | @Fluent 141 | JobService card(JobState state, Handler> handler); 142 | 143 | /** 144 | * Get cardinality of completed jobs. 145 | * 146 | * @param type job type; if null, then return global metrics 147 | * @param handler async result handler 148 | */ 149 | @Fluent 150 | JobService completeCount(String type, Handler> handler); 151 | 152 | /** 153 | * Get cardinality of failed jobs. 154 | * 155 | * @param type job type; if null, then return global metrics 156 | */ 157 | @Fluent 158 | JobService failedCount(String type, Handler> handler); 159 | 160 | /** 161 | * Get cardinality of inactive jobs. 162 | * 163 | * @param type job type; if null, then return global metrics 164 | */ 165 | @Fluent 166 | JobService inactiveCount(String type, Handler> handler); 167 | 168 | /** 169 | * Get cardinality of active jobs. 170 | * 171 | * @param type job type; if null, then return global metrics 172 | */ 173 | @Fluent 174 | JobService activeCount(String type, Handler> handler); 175 | 176 | /** 177 | * Get cardinality of delayed jobs. 178 | * 179 | * @param type job type; if null, then return global metrics 180 | */ 181 | @Fluent 182 | JobService delayedCount(String type, Handler> handler); 183 | 184 | /** 185 | * Get the job types present. 186 | * 187 | * @param handler async result handler 188 | */ 189 | @Fluent 190 | JobService getAllTypes(Handler>> handler); 191 | 192 | /** 193 | * Return job ids with the given {@link JobState}. 194 | * 195 | * @param state job state 196 | * @param handler async result handler 197 | */ 198 | @Fluent 199 | JobService getIdsByState(JobState state, Handler>> handler); 200 | 201 | /** 202 | * Get queue work time in milliseconds. 203 | * 204 | * @param handler async result handler 205 | */ 206 | @Fluent 207 | JobService getWorkTime(Handler> handler); 208 | } 209 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/javascripts/main.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * kue - http - main 3 | * Copyright (c) 2011 LearnBoost 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Active state. 9 | */ 10 | 11 | var active; 12 | 13 | /** 14 | * Active type filter. 15 | */ 16 | 17 | var filter; 18 | 19 | /** 20 | * Number of jobs fetched when "more" is clicked. 21 | */ 22 | 23 | var more = 10; 24 | 25 | /** 26 | * Number of jobs shown. 27 | */ 28 | 29 | var to = more; 30 | 31 | /** 32 | * Sort order. 33 | */ 34 | 35 | var sort = 'asc'; 36 | 37 | /** 38 | * Loading indicator. 39 | */ 40 | 41 | var loading; 42 | 43 | /** 44 | * Initialize UI. 45 | */ 46 | 47 | function init(state) { 48 | var canvas = o('#loading canvas').get(0) 49 | , ctx = canvas.getContext('2d'); 50 | 51 | loading = new LoadingIndicator; 52 | loading.ctx = ctx; 53 | loading.size(canvas.width); 54 | 55 | pollStats(1000); 56 | show(state)(); 57 | o('li.inactive a').click(show('inactive')); 58 | o('li.complete a').click(show('complete')); 59 | o('li.active a').click(show('active')); 60 | o('li.failed a').click(show('failed')); 61 | o('li.delayed a').click(show('delayed')); 62 | 63 | o('#filter').change(function () { 64 | filter = $(this).val(); 65 | }); 66 | 67 | o('#sort').change(function () { 68 | sort = $(this).val(); 69 | o('#jobs .job').remove(); 70 | }); 71 | 72 | onpopstate = function (e) { 73 | if (e.state) show(e.state.state)(); 74 | }; 75 | } 76 | 77 | /** 78 | * Show loading indicator. 79 | */ 80 | 81 | function showLoading() { 82 | var n = 0; 83 | o('#loading').show(); 84 | showLoading.timer = setInterval(function () { 85 | loading.update(++n).draw(loading.ctx); 86 | }, 50); 87 | } 88 | 89 | /** 90 | * Hide loading indicator. 91 | */ 92 | 93 | function hideLoading() { 94 | o('#loading').hide(); 95 | clearInterval(showLoading.timer); 96 | } 97 | 98 | /** 99 | * Infinite scroll. 100 | */ 101 | 102 | function infiniteScroll() { 103 | if (infiniteScroll.bound) return; 104 | var body = o('body'); 105 | hideLoading(); 106 | infiniteScroll.bound = true; 107 | 108 | o(window).scroll(function (e) { 109 | var top = body.scrollTop() 110 | , height = body.innerHeight() 111 | , windowHeight = window.innerHeight 112 | , pad = 30; 113 | 114 | if (top + windowHeight + pad >= height) { 115 | to += more; 116 | infiniteScroll.bound = false; 117 | showLoading(); 118 | o(window).unbind('scroll'); 119 | } 120 | }); 121 | } 122 | 123 | /** 124 | * Show jobs with `state`. 125 | * 126 | * @param {String} state 127 | * @param {Boolean} init 128 | * @return {Function} 129 | */ 130 | 131 | function show(state) { 132 | return function () { 133 | active = state; 134 | if (pollForJobs.timer) { 135 | clearTimeout(pollForJobs.timer); 136 | delete pollForJobs.timer; 137 | } 138 | history.pushState({state: state}, state, state); 139 | o('#jobs .job').remove(); 140 | o('#menu li a').removeClass('active'); 141 | o('#menu li.' + state + ' a').addClass('active'); 142 | pollForJobs(state, 1000); 143 | return false; 144 | } 145 | } 146 | 147 | /** 148 | * Poll for jobs with `state` every `ms`. 149 | * 150 | * @param {String} state 151 | * @param {Number} ms 152 | */ 153 | 154 | function pollForJobs(state, ms) { 155 | o('h1').text(state); 156 | refreshJobs(state, function () { 157 | infiniteScroll(); 158 | if (!pollForJobs.timer) pollForJobs.timer = setTimeout(function () { 159 | delete pollForJobs.timer; 160 | pollForJobs(state, ms); 161 | }, ms); 162 | }); 163 | }; 164 | 165 | /** 166 | * Re-request and refresh job elements. 167 | * 168 | * @param {String} state 169 | * @param {Function} fn 170 | */ 171 | 172 | function refreshJobs(state, fn) { 173 | var jobHeight = o('#jobs .job .block').outerHeight(true) 174 | , top = o(window).scrollTop() 175 | , height = window.innerHeight 176 | , visibleFrom = Math.max(0, Math.floor(top / jobHeight)) 177 | , visibleTo = Math.floor((top + height) / jobHeight) 178 | , url = './jobs/' 179 | + (filter ? filter + '/' : '') 180 | + state + '/0/to/' + to 181 | + '/' + sort; 182 | 183 | // var color = ['blue', 'red', 'yellow', 'green', 'purple'][Math.random() * 5 | 0]; 184 | 185 | request(url, function (jobs) { 186 | var len = jobs.length 187 | , job 188 | , el; 189 | 190 | // remove jobs which have changed their state 191 | o('#jobs .job').each(function (i, el) { 192 | var el = $(el) 193 | , id = (el.attr('id') || '').replace('job-', '') 194 | , found = jobs.some(function (job) { 195 | return job && id == job.id; 196 | }); 197 | if (!found) el.remove(); 198 | }); 199 | 200 | for (var i = 0; i < len; ++i) { 201 | if (!jobs[i]) continue; 202 | 203 | // exists 204 | if (o('#job-' + jobs[i].id).length) { 205 | if (i < visibleFrom || i > visibleTo) continue; 206 | el = o('#job-' + jobs[i].id); 207 | // el.css('background-color', color); 208 | job = el.get(0).job; 209 | job.update(jobs[i]) 210 | .showProgress('active' == active) 211 | .showErrorMessage('failed' == active) 212 | .render(); 213 | // new 214 | } else { 215 | job = new Job(jobs[i]); 216 | el = job.showProgress('active' == active) 217 | .showErrorMessage('failed' == active) 218 | .render(true); 219 | 220 | el.get(0).job = job; 221 | el.appendTo('#jobs'); 222 | } 223 | } 224 | 225 | fn(); 226 | }); 227 | } 228 | 229 | /** 230 | * Poll for stats every `ms`. 231 | * 232 | * @param {Number} ms 233 | */ 234 | 235 | function pollStats(ms) { 236 | request('./stats', function (data) { 237 | o('li.inactive .count').text(data.inactiveCount); 238 | o('li.active .count').text(data.activeCount); 239 | o('li.complete .count').text(data.completeCount); 240 | o('li.failed .count').text(data.failedCount); 241 | o('li.delayed .count').text(data.delayedCount); 242 | setTimeout(function () { 243 | pollStats(ms); 244 | }, ms); 245 | }); 246 | } 247 | 248 | /** 249 | * Request `url` and invoke `fn(res)`. 250 | * 251 | * @param {String} url 252 | * @param {Function} fn 253 | */ 254 | 255 | function request(url, fn) { 256 | var method = 'GET'; 257 | 258 | if ('string' == typeof fn) { 259 | method = url; 260 | url = fn; 261 | fn = arguments[2]; 262 | } 263 | 264 | fn = fn || function () { 265 | }; 266 | 267 | o.ajax({type: method, url: url}) 268 | .success(function (res) { 269 | res.error 270 | ? error(res.error) 271 | : fn(res); 272 | }); 273 | } 274 | 275 | /** 276 | * Display error `msg`. 277 | * 278 | * @param {String} msg 279 | */ 280 | 281 | function error(msg) { 282 | o('#error').text(msg).addClass('show'); 283 | setTimeout(function () { 284 | o('#error').removeClass('show'); 285 | }, 4000); 286 | } 287 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/javascripts/job.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * kue - Job 3 | * Copyright (c) 2011 LearnBoost 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * Initialize a new `Job` with the given `data`. 9 | * 10 | * @param {Object} obj 11 | */ 12 | 13 | function Job(data) { 14 | this.update(data); 15 | } 16 | 17 | /** 18 | * Show progress indicator. 19 | * 20 | * @param {Boolean} val 21 | * @return {Job} for chaining 22 | */ 23 | 24 | Job.prototype.showProgress = function (val) { 25 | this._showProgress = val; 26 | return this; 27 | }; 28 | 29 | /** 30 | * Show error message when `val` is true. 31 | * 32 | * @param {Boolean} val 33 | * @return {Job} for chaining 34 | */ 35 | 36 | Job.prototype.showErrorMessage = function (val) { 37 | this._showError = val; 38 | return this; 39 | }; 40 | 41 | /** 42 | * Remove the job and callback `fn()`. 43 | * 44 | * @param {Function} fn 45 | */ 46 | 47 | Job.prototype.remove = function (fn) { 48 | request('DELETE', './job/' + this.id, fn); 49 | return this; 50 | }; 51 | 52 | /** 53 | * Restart the job and callback `fn()`. 54 | * 55 | * @param {Function} fn 56 | */ 57 | 58 | Job.prototype.restart = function (fn) { 59 | request('POST', './inactive/' + this.id, fn); 60 | return this; 61 | }; 62 | 63 | /** 64 | * Update the job with the given `data`. 65 | * 66 | * @param {Object} data 67 | * @return {Job} for chaining 68 | */ 69 | 70 | Job.prototype.update = function (data) { 71 | for (var key in data) this[key] = data[key]; 72 | if (!this.data) this.data = {}; 73 | return this; 74 | }; 75 | 76 | /** 77 | * Render the job, returning an oQuery object. 78 | * 79 | * @param {Boolean} isNew 80 | * @return {oQuery} 81 | */ 82 | 83 | Job.prototype.render = function (isNew) { 84 | var self = this 85 | , id = this.id 86 | , view = this.view 87 | , keys = Object.keys(this.data).sort() 88 | , data; 89 | 90 | if (isNew) { 91 | view = this.view = View('job'); 92 | 93 | view.remove(function () { 94 | this.remove(); 95 | self.remove(); 96 | }); 97 | 98 | view.restart(function () { 99 | this.restart(); 100 | self.restart(); 101 | }); 102 | 103 | var canvas = view.progress 104 | , ctx = this.ctx = canvas.getContext('2d') 105 | , progress = new Progress; 106 | 107 | progress.size(canvas.width); 108 | this._progress = progress; 109 | 110 | // initially hide the logs 111 | view.log.hide(); 112 | 113 | // populate title and id 114 | view.el.attr('id', 'job-' + id); 115 | view.id(id); 116 | 117 | // show job data 118 | for (var i = 0, len = keys.length; i < len; ++i) { 119 | data = this.data[keys[i]]; 120 | if ('object' == typeof data) data = JSON.stringify(data); 121 | var row = View('row'); 122 | // row.title(keys[i] + ':').value(data); 123 | row.title(keys[i] + ':').value($('

').text(data).html()); 124 | view.data.add(row); 125 | } 126 | 127 | // alter state 128 | view.state(this.state); 129 | view.state().click(function () { 130 | var select = o('', options(states, self.state)); 131 | o(this).replaceWith(select); 132 | select.change(function () { 133 | self.updateState(select.val()); 134 | }); 135 | return false; 136 | }); 137 | 138 | // alter priority 139 | view.priority(priority(this)); 140 | view.priority().click(function () { 141 | var select = o('', options(priorities, self.priority)); 142 | o(this).replaceWith(select); 143 | select.change(function () { 144 | self.updatePriority(select.val()); 145 | }) 146 | return false; 147 | }); 148 | 149 | // show details 150 | view.el.find('.contents').toggle(function () { 151 | view.details().addClass('show'); 152 | self.showDetails = true; 153 | }, function () { 154 | view.details().removeClass('show'); 155 | self.showDetails = false; 156 | }); 157 | } 158 | 159 | this.renderUpdate(); 160 | 161 | return view.el; 162 | }; 163 | 164 | /** 165 | * Update this jobs state to `state`. 166 | * 167 | * @param {String} state 168 | */ 169 | 170 | Job.prototype.updateState = function (state) { 171 | request('PUT', './job/' + this.id + '/state/' + state); 172 | }; 173 | 174 | /** 175 | * Update this jobs priority to `n`. 176 | * 177 | * @param {Number} n 178 | */ 179 | 180 | Job.prototype.updatePriority = function (n) { 181 | request('PUT', './job/' + this.id + '/priority/' + n); 182 | }; 183 | 184 | /** 185 | * Update the job view. 186 | */ 187 | 188 | Job.prototype.renderUpdate = function () { 189 | // tbd: templates 190 | var view = this.view 191 | , showError = this._showError 192 | , showProgress = this._showProgress; 193 | 194 | // type 195 | view.type(this.type); 196 | 197 | // errors 198 | if (showError && this.error) { 199 | view.errorMessage(this.error.split('\n')[0]); 200 | } else { 201 | view.errorMessage().remove(); 202 | } 203 | 204 | // attempts 205 | if (this.attempts.made) { 206 | view.attempts(this.attempts.made + '/' + this.attempts.max); 207 | } else { 208 | view.attempts().parent().remove(); 209 | } 210 | 211 | // title 212 | view.title(this.data.title 213 | ? this.data.title 214 | : 'untitled'); 215 | 216 | // details 217 | this.renderTimestamp('created_at'); 218 | this.renderTimestamp('updated_at'); 219 | this.renderTimestamp('failed_at'); 220 | 221 | // delayed 222 | if ('delayed' == this.state || 'DELAYED' == this.state) { 223 | var delay = parseInt(this.delay, 10) 224 | , creation = parseInt(this.created_at, 10) 225 | , remaining = relative(creation + delay - Date.now()); 226 | view.title((this.data.title || '') + ' ( ' + remaining + ' )'); 227 | } 228 | 229 | // inactive 230 | if ('inactive' == this.state) view.log.remove(); 231 | 232 | // completion 233 | if ('complete' == this.state) { 234 | view.duration(relative(this.duration)); 235 | view.updated_at().prev().text('Completed: '); 236 | view.priority().parent().hide(); 237 | } else { 238 | view.duration().parent().remove(); 239 | } 240 | 241 | // error 242 | if ('failed' == this.state) { 243 | view.error().show().find('pre').text(this.error); 244 | } else { 245 | view.error().hide(); 246 | } 247 | 248 | // progress indicator 249 | if (showProgress) this._progress.update(this.progress).draw(this.ctx); 250 | 251 | // logs 252 | if (this.showDetails) { 253 | request('GET', './job/' + this.id + '/log', function (log) { 254 | var ul = view.log.show(); 255 | 256 | // return early if log hasnt changed 257 | if (ul.text() === log) return; 258 | 259 | ul.find('li').remove(); 260 | log.forEach(function (line) { 261 | ul.append(o('
  • %s
  • ', line)); 262 | }); 263 | }); 264 | } 265 | }; 266 | 267 | /** 268 | * Render timestamp for the given `prop`. 269 | * 270 | * @param {String} prop 271 | */ 272 | 273 | Job.prototype.renderTimestamp = function (prop) { 274 | var val = this[prop] 275 | , view = this.view; 276 | 277 | if (val) { 278 | view[prop]().text(relative(Date.now() - val) + ' ago'); 279 | } else { 280 | view[prop]().parent().remove(); 281 | } 282 | }; 283 | -------------------------------------------------------------------------------- /docs/zh-cn/vertx-kue-features.zh-cn.md: -------------------------------------------------------------------------------- 1 | # Vert.x Kue 特性介绍 2 | 3 | ## 使用方式 4 | 5 | 我们推荐将应用编写为`Verticle`,比如: 6 | 7 | ```java 8 | public class KueExampleVerticle extends AbstractVerticle { 9 | 10 | @Override 11 | public void start() throws Exception { 12 | // 在此编写逻辑 13 | } 14 | 15 | } 16 | ``` 17 | 18 | ## 创建任务 19 | 20 | 首先我们需要使用`Kue.createQueue(vertx, config)`方法创建一个工作队列实例`Kue`: 21 | 22 | ```java 23 | Kue kue = Kue.createQueue(vertx, config()); 24 | ``` 25 | 26 | 然后我们就可以调用`kue.createJob()`方法来创建一个任务(`Job`)。我们需要指定任务的类型,并且可以给任务绑定各种数据(`JsonObject`格式)。任务创建完成以后,我们就可以调用`job.save()`方法来将此任务存储至Redis中。`save`方法是一个基于`Future`的异步方法,所以我们可以给其返回的`Future`绑定一个`Handler`,这样存储操作完成(不管是成功还是失败)以后,对应的`Handler`都会被调用。我们来看一下示例: 27 | 28 | ```java 29 | JsonObject data = new JsonObject() 30 | .put("title", "Learning Vert.x") 31 | .put("content", "Vert.x Core"); 32 | 33 | Job job = kue.createJob("learn vertx", data); 34 | 35 | job.save().setHandler(r0 -> { 36 | if (r0.succeeded()) { 37 | // 处理此job 38 | } else { 39 | // 处理错误 40 | } 41 | }); 42 | ``` 43 | 44 | ### 任务优先级 45 | 46 | 我们可以通过`priority`方法给任务指定优先级,需要传递一个`Priority`类型的参数: 47 | 48 | ```java 49 | JsonObject data = new JsonObject() 50 | .put("title", "Learning Vert.x") 51 | .put("content", "Vert.x Core"); 52 | 53 | Job job = kue.createJob("learn vertx", data) 54 | .priority(Priority.HIGH); 55 | ``` 56 | 57 | `Priority`是一个枚举类,里面定义了五个优先级等级: 58 | 59 | ```java 60 | public enum Priority { 61 | LOW(10), 62 | NORMAL(0), 63 | MEDIUM(-5), 64 | HIGH(-10), 65 | CRITICAL(-15); 66 | } 67 | ``` 68 | 69 | ### 任务日志记录 70 | 71 | 我们可以通过`log`方法将任务的日志(如错误信息,重要信息)记录到Redis中,并且可以在UI端查看: 72 | 73 | ```java 74 | job.log("万恶的任务处理失败了"); 75 | ``` 76 | 77 | ### 任务进度 78 | 79 | 任务进度对于一些长时间的任务来说很有用,比如格式转换、文件操作等。在处理任务的过程中,我们可以调用`progress`方法来改变任务的进度: 80 | 81 | ```java 82 | job.progress(frames, totalFrames); 83 | ``` 84 | 85 | `progress`方法的原型是`Future progress(int completed, int total)`,第一个参数是已经完成的进度,第二个参数是完成时需要的进度。 86 | 87 | ### 任务事件 88 | 89 | 任务事件是非常关键的。我们通过Vert.x的Event Bus来发送和接受事件,每个事件都对应一个特定的地址。目前Vert.x Kue支持以下类型的任务事件(job events): 90 | 91 | - `start` 开始处理一个任务 (`onStart`) 92 | - `promotion` 一个延期的任务时间已到,提升至工作队列中 (`onPromotion`) 93 | - `progress` 任务的进度变化 (`onProgress`) 94 | - `failed_attempt` 任务处理失败,但是还可以重试 (`onFailureAttempt`) 95 | - `failed` 任务处理失败并且不能重试 (`onFailure`) 96 | - `complete` 任务完成 (`onComplete`) 97 | - `remove` 任务从后端存储中移除 (`onRemove`) 98 | 99 | 举个例子: 100 | 101 | ```java 102 | JsonObject data = new JsonObject() 103 | .put("title", "学习 Vert.x") 104 | .put("content", "core"); 105 | 106 | Job j = kue.createJob("learn vertx", data) 107 | .onComplete(r -> { // 完成事件监听器 108 | System.out.println("感觉: " + r.getResult().getString("feeling")); 109 | }).onFailure(r -> { // 失败事件监听器 110 | System.out.println("呵呵。。。有点难。。。"); 111 | }).onProgress(r -> { // 进度变更事件监听器 112 | System.out.println("吼啊!目前进度:" + r); 113 | }); 114 | ``` 115 | 116 | ### 工作队列事件 117 | 118 | 工作队列事件支持的类型与任务事件支持的类型相同,只不过需要加前缀 `job_`,比如: 119 | 120 | ```java 121 | kue.on("job_complete", r -> { 122 | System.out.println("Completed!"); 123 | }); 124 | ``` 125 | 126 | ### 延时任务 127 | 128 | 我们可以给一个任务设定延时,这样处理任务的时候它会被延期处理。我们可以通过`setDelay`方法来设定延时的时间,单位为毫秒(ms)。设定了延时以后,任务的状态将变更为`DELAYED`。 129 | 130 | ```java 131 | Job email = kue.createJob("email", data) 132 | .setDelay(8888) 133 | .priority(Priority.HIGH); 134 | ``` 135 | 136 | 在底层,Vert.x Kue会设置一个定时器,每隔一段时间就会检查一次延期的任务(`checkJobPromotion`方法),如果有任务到达执行时间,那么就将其提升至工作队列中等待处理。 137 | 138 | ### 延时重试机制 139 | 140 | 默认情况下,任务处理失败的时候会立即进行重试,不管是否设定延迟时间`delay`。当然,Vert.x Kue支持延迟重试机制。我们只需要设定`backoff`配置即可: 141 | 142 | ```java 143 | job.setBackoff(new JsonObject().put("delay", 5000).put("type", "fixed")); 144 | ``` 145 | 146 | 目前Vert.x Kue支持两种延迟重试机制:**固定延迟时间** 以及 **指数增长延迟时间**。前者使用固定的延迟时间(`fixed`,默认情况),而后者在给定延迟时间的基础上进行指数增长(`exponential`)。如果开启了延迟重试且未指定基础延迟时间,Vert.x Kue将使用任务中的延时时间(`delay`属性)。 147 | 148 | ## 处理任务 149 | 150 | 使用Vert.x Kue处理任务非常简单。我们可以调用`kue.process(jobType, n, handler)`方法来处理任务。第一个参数对应要处理任务的类型,第二个参数对应同时处理任务的数量上限,第三个参数对应处理该类型任务的逻辑,类型为`Handler`。 151 | 152 | 在下面的例子中,我们将要处理类型为`email`的任务,一次最多处理3个任务。当任务完成时,我们调用`job`的`done()`方法完成任务(发送`done`事件);当处理遇到错误的时候,我们可以调用`done(err)`方法结束处理此任务并标记失败(发送`done_fail`事件): 153 | 154 | ```java 155 | kue.process("email", 3, job -> { 156 | if (job.getData().getString("address") == null) { 157 | job.done(new IllegalStateException("invalid address")); // 失败 158 | } 159 | 160 | // 任务处理逻辑 161 | 162 | job.done(); // 任务完成 163 | }); 164 | ``` 165 | 166 | ## 错误处理 167 | 168 | 所有的错误事件都会被发送至代表工作队列事件地址的**worker address**。我们可以通过`Kue`的`on`方法监听错误事件: 169 | 170 | ```java 171 | kue.on("error", event -> { 172 | // 处理错误 173 | }); 174 | ``` 175 | 176 | ## 工作队列统计数据 177 | 178 | `Kue`实例提供了两种类型的方法来查询每种状态下任务的数量,所有方法都是异步的: 179 | 180 | ```java 181 | kue.inactiveCount(null) // 其它诸如activeCount这样的方法也类似 182 | .setHandler(r -> { 183 | if (r.succeeded()) { 184 | if (r.result() > 1000) 185 | System.out.println("It's too bad!"); 186 | } 187 | }); 188 | ``` 189 | 190 | 当然我们也可以指定任务的类型: 191 | 192 | ```java 193 | kue.failedCount("my-job") 194 | .setHandler(r -> { 195 | if (r.succeeded()) { 196 | if (r.result() > 1000) 197 | System.out.println("It's too bad!"); 198 | } 199 | }); 200 | ``` 201 | 202 | 我们也可以通过`getIdsByState`方法获取某个状态下的所有任务的id,返回类型为`Future>`: 203 | 204 | ```java 205 | kue.getIdsByState(JobState.ACTIVE) 206 | .setHandler(r -> { 207 | // ... 208 | }); 209 | ``` 210 | 211 | 在实际生产环境下任务数量特别多,获取所有任务可能显得不切实际。因此,我们还可以通过`range`系列的方法来获取某些特定范围内的任务,比如: 212 | 213 | ```java 214 | kue.jobRangeByState("complete", 0, 10, "asc") // 按顺序获取10个任务 215 | .setHandler(r -> { 216 | // ... 217 | }); 218 | ``` 219 | 220 | 或者: 221 | 222 | ```java 223 | kue.jobRangeByType("moha", "complete", 0, 10, "asc") 224 | .setHandler(r -> { 225 | // ... 226 | }); 227 | ``` 228 | 229 | ## Redis连接设置 230 | 231 | Vert.x Kue使用了Vert.x Redis Client作为Redis通信组件,因此我们可以参考[Vert.x-Redis 官方文档](http://vertx.io/docs/vertx-redis-client/java/)查看配置信息。我们推荐使用JSON格式的配置文件: 232 | 233 | ```json 234 | { 235 | "redis.host": "127.0.0.1", 236 | "redis.port": 6379 237 | } 238 | ``` 239 | 240 | 这样当我们部署Verticle的时候,Vert.x Launcher就可以方便地读取这些配置了。 241 | 242 | ## 用户界面 243 | 244 | Vert.x Kue的用户界面复用了 [Automattic/kue](https://github.com/Automattic/kue) 的用户界面,仅更改了一小部分代码。感谢Automattic以及整个开源社区! 245 | 246 | ![](../images/vertx_kue_ui_1.png) 247 | 248 | 整个用户界面和API一起相当于一个Vert.x Web应用。 249 | 250 | ## REST API 251 | 252 | Vert.x Kue同样也提供一组REST API供UI组件和用户调用。 253 | 254 | ### GET /stats 255 | 256 | 获取当前的统计数据: 257 | 258 | ```json 259 | { 260 | "workTime" : 699960, 261 | "inactiveCount" : 0, 262 | "completeCount" : 404, 263 | "activeCount" : 13, 264 | "failedCount" : 0, 265 | "delayedCount" : 0 266 | } 267 | ``` 268 | 269 | ### GET /job/:id 270 | 271 | 获取某个任务的详细信息: 272 | ```json 273 | { 274 | "address_id" : "a245319e-341d-49f9-b6bb-371247a6a358", 275 | "attempts" : 0, 276 | "created_at" : 1466348210024, 277 | "data" : { 278 | "title" : "Account renewal required", 279 | "template" : "renewal-email", 280 | "to" : "qinxin@jianpo.xyz" 281 | }, 282 | "delay" : 8888, 283 | "duration" : 2027, 284 | "failed_at" : 0, 285 | "id" : 403, 286 | "max_attempts" : 1, 287 | "priority" : "HIGH", 288 | "progress" : 100, 289 | "promote_at" : 1466348218912, 290 | "removeOnComplete" : false, 291 | "started_at" : 1466348219067, 292 | "state" : "COMPLETE", 293 | "type" : "email", 294 | "updated_at" : 1466348221099, 295 | "zid" : "03|403" 296 | } 297 | ``` 298 | 299 | ### GET /job/:id/log 300 | 301 | 获取指定任务的日志: 302 | 303 | ```json 304 | [ 305 | "error | f1", 306 | "error | f2", 307 | "error | f3" 308 | ] 309 | ``` 310 | 311 | ### GET /jobs/:from/to/:to/:order? 312 | 313 | 获取某个范围(`:from`到`:to`)的任务,并且指定排序顺序(`asc`和`desc`)。比如`/jobs/0/to/2/asc`。 314 | 315 | ### GET /jobs/:state/:from/to/:to/:order? 316 | 317 | 和上面的功能相似,但是需要指定任务状态`:state`: 318 | 319 | - active 320 | - inactive 321 | - failed 322 | - complete 323 | 324 | ### GET /jobs/:type/:state/:from/to/:to/:order? 325 | 326 | 和上面的功能相似,但是需要指定任务状态`:state`以及任务类型`:type`。 327 | 328 | ### DELETE /job/:id 329 | 330 | 删除某个特定id的任务: 331 | 332 | $ curl -X DELETE http://localhost:8080/job/6 333 | {"message":"job 6 removed"} 334 | 335 | ### PUT /job 336 | 337 | 创建一个新任务。 338 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/javascripts/caustic.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * EventEmitter 3 | * Copyright (c) 2011 TJ Holowaychuk 4 | * MIT Licensed 5 | */ 6 | 7 | /** 8 | * EventEmitter. 9 | */ 10 | 11 | function EventEmitter() { 12 | this.callbacks = {}; 13 | } 14 | 15 | /** 16 | * Listen on the given `event` with `fn`. 17 | * 18 | * @param {String} event 19 | * @param {Function} fn 20 | */ 21 | 22 | EventEmitter.prototype.on = function (event, fn) { 23 | (this.callbacks[event] = this.callbacks[event] || []) 24 | .push(fn); 25 | return this; 26 | }; 27 | 28 | /** 29 | * Emit `event` with the given args. 30 | * 31 | * @param {String} event 32 | * @param {Mixed} ... 33 | */ 34 | 35 | EventEmitter.prototype.emit = function (event) { 36 | var args = Array.prototype.slice.call(arguments, 1) 37 | , callbacks = this.callbacks[event]; 38 | 39 | if (callbacks) { 40 | for (var i = 0, len = callbacks.length; i < len; ++i) { 41 | callbacks[i].apply(this, args) 42 | } 43 | } 44 | 45 | return this; 46 | }; 47 | 48 | /*! 49 | * caustic 50 | * Copyright(c) 2011 TJ Holowaychuk 51 | * MIT Licensed 52 | */ 53 | 54 | // html, as there's no need to keep traversing each time. 55 | 56 | /** 57 | * Convert callback `fn` to a function when a string is given. 58 | * 59 | * @param {Type} name 60 | * @return {Type} 61 | * @api private 62 | */ 63 | 64 | function callback(fn) { 65 | return 'string' == typeof fn 66 | ? function (obj) { 67 | return obj[fn](); 68 | } 69 | : fn; 70 | } 71 | 72 | /** 73 | * Initialize a new view with the given `name` 74 | * or string of html. When a `name` is given an element 75 | * with the id `name + "-template"` will be used. 76 | * 77 | * Examples: 78 | * 79 | * var user = new View('user'); 80 | * var list = new View('
    '); 81 | * 82 | * @param {String} name 83 | * @api public 84 | */ 85 | 86 | function View(name) { 87 | if (!(this instanceof View)) return new View(name); 88 | EventEmitter.call(this); 89 | var html; 90 | if (~name.indexOf('<')) html = name; 91 | else html = $('#' + name + '-template').html(); 92 | this.el = $(html); 93 | this.visit(this.el); 94 | } 95 | 96 | /** 97 | * Inherit from `EventEmitter.prototype`. 98 | */ 99 | 100 | View.prototype.__proto__ = EventEmitter.prototype; 101 | 102 | /** 103 | * Visit `el`. 104 | * 105 | * @param {jQuery} el 106 | * @param {Boolean} ignore 107 | * @api private 108 | */ 109 | 110 | View.prototype.visit = function (el, ignore) { 111 | var self = this 112 | , type = el.get(0).nodeName 113 | , classes = el.attr('class').split(/ +/) 114 | , method = 'visit' + type; 115 | 116 | if (this[method] && !ignore) this[method](el, classes[0]); 117 | 118 | el.children().each(function (i, el) { 119 | self.visit($(el)); 120 | }); 121 | }; 122 | 123 | /** 124 | * Visit INPUT tag. 125 | * 126 | * @param {jQuery} el 127 | * @api public 128 | */ 129 | 130 | View.prototype.visitINPUT = function (el) { 131 | var self = this 132 | , name = el.attr('name') 133 | , type = el.attr('type'); 134 | 135 | switch (type) { 136 | case 'text': 137 | this[name] = function (val) { 138 | if (0 == arguments.length) return el.val(); 139 | el.val(val); 140 | return this; 141 | } 142 | 143 | this[name].isEmpty = function () { 144 | return '' == el.val(); 145 | }; 146 | 147 | this[name].clear = function () { 148 | el.val(''); 149 | return self; 150 | }; 151 | break; 152 | case 'checkbox': 153 | this[name] = function (val) { 154 | if (0 == arguments.length) return el.attr('checked'); 155 | switch (typeof val) { 156 | case 'function': 157 | el.change(function (e) { 158 | val.call(self, el.attr('checked'), e); 159 | }); 160 | break; 161 | default: 162 | el.attr('checked', val 163 | ? 'checked' 164 | : val); 165 | } 166 | return this; 167 | } 168 | break; 169 | } 170 | }; 171 | 172 | /** 173 | * Visit FORM. 174 | * 175 | * @param {jQuery} el 176 | * @api private 177 | */ 178 | 179 | View.prototype.visitFORM = function (el, name) { 180 | var self = this; 181 | this.submit = function (val) { 182 | switch (typeof val) { 183 | case 'function': 184 | el.submit(function (e) { 185 | val.call(self, e, el); 186 | return false; 187 | }); 188 | break; 189 | } 190 | } 191 | }; 192 | 193 | /** 194 | * Visit A tag. 195 | * 196 | * @param {jQuery} el 197 | * @api private 198 | */ 199 | 200 | View.prototype.visitA = function (el, name) { 201 | var self = this; 202 | 203 | el.click(function (e) { 204 | self.emit(name, e, el); 205 | }); 206 | 207 | this[name] = function (fn) { 208 | el.click(function (e) { 209 | fn.call(self, e, el); 210 | return false; 211 | }); 212 | return this; 213 | } 214 | }; 215 | 216 | /** 217 | * Visit P, TD, SPAN, or DIV tag. 218 | * 219 | * @param {jQuery} el 220 | * @api private 221 | */ 222 | 223 | View.prototype.visitP = 224 | View.prototype.visitTD = 225 | View.prototype.visitSPAN = 226 | View.prototype.visitDIV = function (el, name) { 227 | var self = this; 228 | this[name] = function (val) { 229 | if (0 == arguments.length) return el; 230 | el.empty().append(val.el || val); 231 | return this; 232 | }; 233 | }; 234 | 235 | /** 236 | * Visit UL tag. 237 | * 238 | * @param {jQuery} el 239 | * @api private 240 | */ 241 | 242 | View.prototype.visitUL = function (el, name) { 243 | var self = this; 244 | this.children = []; 245 | 246 | this[name] = el; 247 | 248 | /** 249 | * Add `val` to this list. 250 | * 251 | * @param {String|jQuery|View} val 252 | * @return {View} for chaining 253 | * @api public 254 | */ 255 | 256 | el.add = function (val) { 257 | var li = $('
  • '); 258 | self.children.push(val); 259 | el.append(li.append(val.el || val)); 260 | return this; 261 | }; 262 | 263 | /** 264 | * Return the list item `View`s as an array. 265 | * 266 | * @return {Array} 267 | * @api public 268 | */ 269 | 270 | el.items = function () { 271 | return self.children; 272 | }; 273 | 274 | /** 275 | * Iterate the list `View`s, calling `fn(item, i)`. 276 | * 277 | * @param {Function} fn 278 | * @return {View} for chaining 279 | * @api public 280 | */ 281 | 282 | el.each = function (fn) { 283 | for (var i = 0, len = self.children.length; i < len; ++i) { 284 | fn(self.children[i], i); 285 | } 286 | return this; 287 | }; 288 | 289 | /** 290 | * Map the list `View`s, calling `fn(item, i)`. 291 | * 292 | * @param {String|function} fn 293 | * @return {Array} 294 | * @api public 295 | */ 296 | 297 | el.map = function (fn) { 298 | var ret = [] 299 | , fn = callback(fn); 300 | 301 | for (var i = 0, len = self.children.length; i < len; ++i) { 302 | ret.push(fn(self.children[i], i)); 303 | } 304 | 305 | return ret; 306 | }; 307 | }; 308 | 309 | /** 310 | * Visit TABLE. 311 | * 312 | * @param {jQuery} el 313 | * @api private 314 | */ 315 | 316 | View.prototype.visitTABLE = function (el, name) { 317 | this[name] = el; 318 | 319 | this[name].add = function (val) { 320 | this.append(val.el || val); 321 | }; 322 | }; 323 | 324 | /** 325 | * Visit CANVAS. 326 | * 327 | * @param {jQuery} el 328 | * @api private 329 | */ 330 | 331 | View.prototype.visitCANVAS = function (el, name) { 332 | this[name] = el.get(0); 333 | }; 334 | 335 | /** 336 | * Visit H1-H5 tags. 337 | * 338 | * @param {jQuery} el 339 | * @api private 340 | */ 341 | 342 | View.prototype.visitH1 = 343 | View.prototype.visitH2 = 344 | View.prototype.visitH3 = 345 | View.prototype.visitH4 = 346 | View.prototype.visitH5 = function (el, name) { 347 | var self = this; 348 | this[name] = function (val) { 349 | if (0 == arguments.length) return el.text(); 350 | el.text(val.el || val); 351 | return this; 352 | }; 353 | }; 354 | 355 | /** 356 | * Remove the view from the DOM. 357 | * 358 | * @return {View} 359 | * @api public 360 | */ 361 | 362 | View.prototype.remove = function () { 363 | var parent = this.el.parent() 364 | , type = parent.get(0).nodeName; 365 | if ('LI' == type) parent.remove(); 366 | else this.el.remove(); 367 | return this; 368 | }; 369 | 370 | /** 371 | * Append this view's element to `val`. 372 | * 373 | * @param {String|jQuery} val 374 | * @return {View} 375 | * @api public 376 | */ 377 | 378 | View.prototype.appendTo = function (val) { 379 | this.el.appendTo(val.el || val); 380 | return this; 381 | }; 382 | -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/queue/KueWorker.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.queue; 2 | 3 | import io.vertx.blueprint.kue.Kue; 4 | import io.vertx.blueprint.kue.util.RedisHelper; 5 | 6 | import io.vertx.core.AbstractVerticle; 7 | import io.vertx.core.AsyncResult; 8 | import io.vertx.core.Future; 9 | import io.vertx.core.Handler; 10 | import io.vertx.core.eventbus.EventBus; 11 | import io.vertx.core.eventbus.MessageConsumer; 12 | import io.vertx.core.json.JsonArray; 13 | import io.vertx.core.json.JsonObject; 14 | import io.vertx.core.logging.Logger; 15 | import io.vertx.core.logging.LoggerFactory; 16 | import io.vertx.redis.RedisClient; 17 | 18 | import java.util.Optional; 19 | 20 | 21 | /** 22 | * The verticle for processing Kue tasks. 23 | * 24 | * @author Eric Zhao 25 | */ 26 | public class KueWorker extends AbstractVerticle { 27 | 28 | private static Logger logger = LoggerFactory.getLogger(Job.class); 29 | 30 | private final Kue kue; 31 | private RedisClient client; // Every worker use different clients. 32 | private EventBus eventBus; 33 | private Job job; 34 | private final String type; 35 | private final Handler jobHandler; 36 | 37 | private MessageConsumer doneConsumer; // Preserve for unregister the consumer. 38 | private MessageConsumer doneFailConsumer; 39 | 40 | public KueWorker(String type, Handler jobHandler, Kue kue) { 41 | this.type = type; 42 | this.jobHandler = jobHandler; 43 | this.kue = kue; 44 | } 45 | 46 | @Override 47 | public void start() throws Exception { 48 | this.eventBus = vertx.eventBus(); 49 | this.client = RedisHelper.client(vertx, config()); 50 | 51 | prepareAndStart(); 52 | } 53 | 54 | /** 55 | * Prepare job and start processing procedure. 56 | */ 57 | private void prepareAndStart() { 58 | cleanup(); 59 | this.getJobFromBackend().setHandler(jr -> { 60 | if (jr.succeeded()) { 61 | if (jr.result().isPresent()) { 62 | this.job = jr.result().get(); 63 | process(); 64 | } else { 65 | this.emitJobEvent("error", null, new JsonObject().put("message", "job_not_exist")); 66 | throw new IllegalStateException("job not exist"); 67 | } 68 | } else { 69 | this.emitJobEvent("error", null, new JsonObject().put("message", jr.cause().getMessage())); 70 | jr.cause().printStackTrace(); 71 | } 72 | }); 73 | } 74 | 75 | /** 76 | * Process the job. 77 | */ 78 | private void process() { 79 | long curTime = System.currentTimeMillis(); 80 | this.job.setStarted_at(curTime) 81 | .set("started_at", String.valueOf(curTime)) 82 | .compose(Job::active) 83 | .setHandler(r -> { 84 | if (r.succeeded()) { 85 | Job j = r.result(); 86 | // emit start event 87 | this.emitJobEvent("start", j, null); 88 | 89 | logger.debug("KueWorker::process[instance:Verticle(" + this.deploymentID() + ")] with job " + job.getId()); 90 | // process logic invocation 91 | try { 92 | jobHandler.handle(j); 93 | } catch (Exception ex) { 94 | j.done(ex); 95 | } 96 | // subscribe the job done event 97 | 98 | doneConsumer = eventBus.consumer(Kue.workerAddress("done", j), msg -> { 99 | createDoneCallback(j).handle(Future.succeededFuture( 100 | ((JsonObject) msg.body()).getJsonObject("result"))); 101 | }); 102 | doneFailConsumer = eventBus.consumer(Kue.workerAddress("done_fail", j), msg -> { 103 | createDoneCallback(j).handle(Future.failedFuture( 104 | (String) msg.body())); 105 | }); 106 | } else { 107 | this.emitJobEvent("error", this.job, new JsonObject().put("message", r.cause().getMessage())); 108 | r.cause().printStackTrace(); 109 | } 110 | }); 111 | } 112 | 113 | private void cleanup() { 114 | Optional.ofNullable(doneConsumer).ifPresent(MessageConsumer::unregister); 115 | Optional.ofNullable(doneFailConsumer).ifPresent(MessageConsumer::unregister); 116 | this.job = null; 117 | } 118 | 119 | private void error(Throwable ex, Job job) { 120 | JsonObject err = new JsonObject().put("message", ex.getMessage()) 121 | .put("id", job.getId()); 122 | eventBus.send(Kue.workerAddress("error"), err); 123 | } 124 | 125 | private void fail(Throwable ex) { 126 | job.failedAttempt(ex).setHandler(r -> { 127 | if (r.failed()) { 128 | this.error(r.cause(), job); 129 | } else { 130 | Job res = r.result(); 131 | if (res.hasAttempts()) { 132 | this.emitJobEvent("failed_attempt", job, new JsonObject().put("message", ex.getMessage())); // shouldn't include err? 133 | } else { 134 | this.emitJobEvent("failed", job, new JsonObject().put("message", ex.getMessage())); 135 | } 136 | prepareAndStart(); 137 | } 138 | }); 139 | } 140 | 141 | /** 142 | * Redis zpop atomic primitive with transaction. 143 | * 144 | * @param key redis key 145 | * @return the async result of zpop 146 | */ 147 | private Future zpop(String key) { 148 | Future future = Future.future(); 149 | client.transaction() 150 | .multi(_failure()) 151 | .zrange(key, 0, 0, _failure()) 152 | .zremrangebyrank(key, 0, 0, _failure()) 153 | .exec(r -> { 154 | if (r.succeeded()) { 155 | JsonArray res = r.result(); 156 | if (res.getJsonArray(0).size() == 0) // empty set 157 | future.fail(new IllegalStateException("Empty zpop set")); 158 | else { 159 | try { 160 | future.complete(Long.parseLong(RedisHelper.stripFIFO( 161 | res.getJsonArray(0).getString(0)))); 162 | } catch (Exception ex) { 163 | future.fail(ex); 164 | } 165 | } 166 | } else { 167 | future.fail(r.cause()); 168 | } 169 | }); 170 | return future; 171 | } 172 | 173 | /** 174 | * Get a job from Redis backend by priority. 175 | * 176 | * @return async result of job 177 | */ 178 | private Future> getJobFromBackend() { 179 | Future> future = Future.future(); 180 | client.blpop(RedisHelper.getKey(this.type + ":jobs"), 0, r1 -> { 181 | if (r1.failed()) { 182 | client.lpush(RedisHelper.getKey(this.type + ":jobs"), "1", r2 -> { 183 | if (r2.failed()) 184 | future.fail(r2.cause()); 185 | }); 186 | } else { 187 | this.zpop(RedisHelper.getKey("jobs:" + this.type + ":INACTIVE")) 188 | .compose(kue::getJob) 189 | .setHandler(r -> { 190 | if (r.succeeded()) { 191 | future.complete(r.result()); 192 | } else 193 | future.fail(r.cause()); 194 | }); 195 | } 196 | }); 197 | return future; 198 | } 199 | 200 | private Handler> createDoneCallback(Job job) { 201 | return r0 -> { 202 | if (job == null) { 203 | // maybe should warn 204 | return; 205 | } 206 | if (r0.failed()) { 207 | this.fail(r0.cause()); 208 | return; 209 | } 210 | long dur = System.currentTimeMillis() - job.getStarted_at(); 211 | job.setDuration(dur) 212 | .set("duration", String.valueOf(dur)); 213 | JsonObject result = r0.result(); 214 | if (result != null) { 215 | job.setResult(result) 216 | .set("result", result.encodePrettily()); 217 | } 218 | 219 | job.complete().setHandler(r -> { 220 | if (r.succeeded()) { 221 | Job j = r.result(); 222 | if (j.isRemoveOnComplete()) { 223 | j.remove(); 224 | } 225 | this.emitJobEvent("complete", j, null); 226 | 227 | this.prepareAndStart(); // prepare for next job 228 | } 229 | }); 230 | }; 231 | } 232 | 233 | @Override 234 | public void stop() throws Exception { 235 | // stop hook 236 | cleanup(); 237 | } 238 | 239 | /** 240 | * Emit job event. 241 | * 242 | * @param event event type 243 | * @param job corresponding job 244 | * @param extra extra data 245 | */ 246 | private void emitJobEvent(String event, Job job, JsonObject extra) { 247 | JsonObject data = new JsonObject().put("extra", extra); 248 | if (job != null) { 249 | data.put("job", job.toJson()); 250 | } 251 | eventBus.send(Kue.workerAddress("job_" + event), data); 252 | switch (event) { 253 | case "failed": 254 | case "failed_attempt": 255 | eventBus.send(Kue.getCertainJobAddress(event, job), data); 256 | break; 257 | case "error": 258 | eventBus.send(Kue.workerAddress("error"), data); 259 | break; 260 | default: 261 | eventBus.send(Kue.getCertainJobAddress(event, job), job.toJson()); 262 | } 263 | } 264 | 265 | private static Handler> _failure() { 266 | return r -> { 267 | if (r.failed()) 268 | r.cause().printStackTrace(); 269 | }; 270 | } 271 | 272 | } 273 | -------------------------------------------------------------------------------- /docs/en/vertx-kue-features-en.md: -------------------------------------------------------------------------------- 1 | # Vert.x Kue Features 2 | 3 | ## Scope 4 | 5 | We recommend the applications are wrapped in verticles. 6 | 7 | ```java 8 | public class KueExampleVerticle extends AbstractVerticle { 9 | 10 | @Override 11 | public void start() throws Exception { 12 | // write your logic here 13 | } 14 | 15 | } 16 | ``` 17 | 18 | ## Creating Jobs 19 | 20 | First we should create a job queue instance(`Kue`) with `Kue.createQueue(vertx, config)`: 21 | 22 | ```java 23 | Kue kue = Kue.createQueue(vertx, config()); 24 | ``` 25 | 26 | Then we could call `kue.createJob()` to create a job, with a specific job type and arbitrary job data. 27 | After that, we could save a `Job` into Redis backend with a default priority level of "normal" using `Job#save` method. 28 | The `save` method is a `Future` based asynchronous method so we could attach a `Handler` on it and once the save operation is successful or failed, 29 | the logic in attached `Handler` will be called. 30 | 31 | ```java 32 | JsonObject data = new JsonObject() 33 | .put("title", "Learning Vert.x") 34 | .put("content", "core"); 35 | 36 | Job job = kue.createJob("learn vertx", data); 37 | 38 | job.save().setHandler(r0 -> { 39 | if (r0.succeeded()) { 40 | // process the job 41 | } else { 42 | // process failure 43 | } 44 | }); 45 | ``` 46 | 47 | ### Job Priority 48 | 49 | To specify the priority of a job, simply invoke the `priority` method with a `Priority` enum class: 50 | 51 | ```java 52 | JsonObject data = new JsonObject() 53 | .put("title", "Learning Vert.x") 54 | .put("content", "core"); 55 | 56 | Job job = kue.createJob("learn vertx", data) 57 | .priority(Priority.HIGH); 58 | ``` 59 | 60 | There are five levels in `Priority` class: 61 | 62 | ```java 63 | public enum Priority { 64 | LOW(10), 65 | NORMAL(0), 66 | MEDIUM(-5), 67 | HIGH(-10), 68 | CRITICAL(-15); 69 | } 70 | ``` 71 | 72 | ### Job Logs 73 | 74 | Job-specific logs enable you to expose information to the UI at any point in the job's life-time. To do so we can simply invoke `job.log(str)` method, which accepts a message string: 75 | 76 | ```java 77 | job.log("job ttl failed"); 78 | ``` 79 | 80 | ### Job Progress 81 | 82 | Job progress is extremely useful for long-running jobs such as video conversion. To update the job's progress we can invoke `job.progress(completed, total)`: 83 | 84 | ```java 85 | job.progress(frames, totalFrames); 86 | ``` 87 | 88 | The `progress` method returns `Future` so we can also set a handler on it. 89 | 90 | ### Job Events 91 | 92 | Job-specific events are registered on the Event Bus of Vert.x. We support the following events: 93 | 94 | - `start` the job is now running (`onStart`) 95 | - `promotion` the job is promoted from delayed state to queued (`onPromotion`) 96 | - `progress` the job's progress ranging from 0-100 (`onProgress`) 97 | - `failed_attempt` the job has failed, but has remaining attempts yet (`onFailureAttempt`) 98 | - `failed` the job has failed and has no remaining attempts (`onFailure`) 99 | - `complete` the job has completed (`onComplete`) 100 | - `remove` the job has been removed (`onRemove`) 101 | 102 | Here is an example: 103 | 104 | ```java 105 | JsonObject data = new JsonObject() 106 | .put("title", "Learning Vert.x") 107 | .put("content", "core"); 108 | 109 | Job j = kue.createJob("learn vertx", data) 110 | .onComplete(r -> { // on complete handler 111 | System.out.println("Feeling: " + r.getResult().getString("feeling", "none")); 112 | }).onFailure(r -> { // on failure handler 113 | System.out.println("eee...so difficult..."); 114 | }).onProgress(r -> { // on progress modifying handler 115 | System.out.println("I love this! My progress => " + r); 116 | }); 117 | ``` 118 | 119 | ### Queue Events 120 | 121 | With prefix `job_`: 122 | 123 | ```java 124 | kue.on("job_complete", r -> { 125 | System.out.println("Completed!"); 126 | }); 127 | ``` 128 | 129 | ### Delayed Jobs 130 | 131 | We can schedule to process a job with delays by calling `job.setDelay(ms)` method. The job's state will be `DELAYED`. 132 | 133 | ```java 134 | Job email = kue.createJob("email", data) 135 | .setDelay(8888) 136 | .priority(Priority.HIGH); 137 | ``` 138 | 139 | Vert.x Kue will check the delayed jobs(`checkJobPromotion`) with a timer, promoting them if the scheduled delay has been exceeded, defaulting to a check of top 1000 jobs every second. 140 | 141 | ### Failure Backoff 142 | 143 | Job retry attempts are done as soon as they fail, with no delay, even if your job had a delay set via `Job#delay`. If you want to delay job re-attempts upon failures (known as backoff) you can use set a `backoff` config on the object: 144 | 145 | ```java 146 | job.setBackoff(new JsonObject().put("delay", 5000).put("type", "fixed")); 147 | ``` 148 | 149 | At present we support two kinds of backoff: `fixed` and `exponential`. The `fixed` type uses a fixed delay timeout, and we can specify it via the `delay` attribute in `backoff` field. If not specified, Vert.x Kue will use the original `delay` field in the job. The `exponential` type enables exponential backoff. 150 | 151 | ## Processing Jobs 152 | 153 | It's very simple to process jobs in Vert.x Kue. We can use `kue.process(jobType, n, handler)` to process jobs concurrently. The first parameter refers to the type of job and the second parameter refers to the maximum active job count, while the third parameter refers to the handler that process the job. 154 | 155 | In the following example we are going to process jobs in `email` type. We process 3 jobs at the same time. In the handler, we could invoke `done()` method to finish the job. If we encountered error, we could invoke `done(err)` to fail the job: 156 | 157 | ```java 158 | kue.process("email", 3, job -> { 159 | if (job.getData().getString("address") == null) { 160 | job.done(new IllegalStateException("invalid address")); // fail 161 | } 162 | 163 | // process logic... 164 | 165 | job.done(); // finish 166 | }); 167 | ``` 168 | 169 | ## Error Handling 170 | 171 | Error events will send on the worker address and we could add listener on it using `Kue#on(error, handler)`: 172 | 173 | ```java 174 | kue.on("error", event -> { 175 | // process error 176 | }); 177 | ``` 178 | 179 | ## Queue Metrics 180 | 181 | `Kue` object has two type of methods to tell us about the number of jobs in each state: 182 | 183 | ```java 184 | kue.inactiveCount(null) 185 | .setHandler(r -> { 186 | if (r.succeeded()) { 187 | if (r.result() > 1000) 188 | System.out.println("It's too bad!"); 189 | } 190 | }); 191 | ``` 192 | 193 | It also supports query on a specific job type: 194 | 195 | ```java 196 | kue.failedCount("my-job") 197 | .setHandler(r -> { 198 | if (r.succeeded()) { 199 | if (r.result() > 1000) 200 | System.out.println("It's too bad!"); 201 | } 202 | }); 203 | ``` 204 | 205 | and iterating over job ids: 206 | 207 | ```java 208 | kue.getIdsByState(JobState.ACTIVE) 209 | .setHandler(r -> { 210 | // ... 211 | }); 212 | ``` 213 | 214 | ## Redis Connection Settings 215 | 216 | In Vert.x Kue, we use Vert.x Redis Client as the redis component so we can refer to [Vert.x-redis document](http://vertx.io/docs/vertx-redis-client/java/). We recommend to use a json config file like this: 217 | 218 | ```json 219 | { 220 | "redis.host": "127.0.0.1", 221 | "redis.port": 6379 222 | } 223 | ``` 224 | 225 | Then we can pass the config file to Vert.x Launcher when we deploy our verticles. 226 | 227 | ## User-Interface 228 | 229 | The UI of Vert.x Kue is from the original [Automattic/kue](https://github.com/Automattic/kue). Thanks to Automattic/kue and the open-source community! 230 | 231 | If we want to use Kue UI, we can simply run `kue-http` component: 232 | 233 | java -jar kue-http/build/libs/vertx-blueprint-kue-http.jar -cluster -ha -conf config/config.json 234 | 235 | Then you can visit `localhost:8080`: 236 | 237 | ![](../images/vertx_kue_ui_1.png) 238 | 239 | ## Vert.x Kue REST API 240 | 241 | ### GET /stats 242 | 243 | Get global stats(`workTime` and counts of every job state): 244 | 245 | ```json 246 | { 247 | "workTime" : 699960, 248 | "inactiveCount" : 0, 249 | "completeCount" : 404, 250 | "activeCount" : 13, 251 | "failedCount" : 0, 252 | "delayedCount" : 0 253 | } 254 | ``` 255 | 256 | ### GET /job/:id 257 | 258 | Get a job with certain `id`: 259 | 260 | ```json 261 | { 262 | "address_id" : "a245319e-341d-49f9-b6bb-371247a6a358", 263 | "attempts" : 0, 264 | "created_at" : 1466348210024, 265 | "data" : { 266 | "title" : "Account renewal required", 267 | "template" : "renewal-email", 268 | "to" : "qinxin@jianpo.xyz" 269 | }, 270 | "delay" : 8888, 271 | "duration" : 2027, 272 | "failed_at" : 0, 273 | "id" : 403, 274 | "max_attempts" : 1, 275 | "priority" : "HIGH", 276 | "progress" : 100, 277 | "promote_at" : 1466348218912, 278 | "removeOnComplete" : false, 279 | "started_at" : 1466348219067, 280 | "state" : "COMPLETE", 281 | "type" : "email", 282 | "updated_at" : 1466348221099, 283 | "zid" : "03|403" 284 | } 285 | ``` 286 | 287 | ### GET /job/:id/log 288 | 289 | Get job `:id`'s log: 290 | 291 | ```json 292 | [ 293 | "error | f1", 294 | "error | f2", 295 | "error | f3" 296 | ] 297 | ``` 298 | 299 | ### GET /jobs/:from/to/:to/:order? 300 | 301 | Get jobs with the specified range `:from` to `:to`, for example `/jobs/0/to/2/asc`, where `:order` may be **asc** or **desc**. 302 | 303 | ### GET /jobs/:state/:from/to/:to/:order? 304 | 305 | Same as above, restricting by `:state` which is one of: 306 | 307 | - active 308 | - inactive 309 | - failed 310 | - complete 311 | 312 | ### GET /jobs/:type/:state/:from/to/:to/:order? 313 | 314 | Same as above, however restricted to `:type` and `:state`. 315 | 316 | ### DELETE /job/:id 317 | 318 | Delete a job: 319 | 320 | $ curl -X DELETE http://localhost:8080/job/6 321 | {"message":"job 6 removed"} 322 | 323 | ### PUT /job 324 | 325 | Create a job. 326 | -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/service/impl/JobServiceImpl.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue.service.impl; 2 | 3 | import io.vertx.blueprint.kue.queue.Job; 4 | import io.vertx.blueprint.kue.queue.JobState; 5 | import io.vertx.blueprint.kue.service.JobService; 6 | import io.vertx.blueprint.kue.util.RedisHelper; 7 | import io.vertx.core.AsyncResult; 8 | import io.vertx.core.Future; 9 | import io.vertx.core.Handler; 10 | import io.vertx.core.Vertx; 11 | import io.vertx.core.json.JsonArray; 12 | import io.vertx.core.json.JsonObject; 13 | import io.vertx.redis.RedisClient; 14 | 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.stream.Collectors; 18 | 19 | /** 20 | * Redis backend implementation of {@link JobService}. 21 | * 22 | * @author Eric Zhao 23 | */ 24 | public final class JobServiceImpl implements JobService { 25 | 26 | private final Vertx vertx; 27 | private final JsonObject config; 28 | private final RedisClient client; 29 | 30 | public JobServiceImpl(Vertx vertx) { 31 | this(vertx, new JsonObject()); 32 | } 33 | 34 | public JobServiceImpl(Vertx vertx, JsonObject config) { 35 | this.vertx = vertx; 36 | this.config = config; 37 | this.client = RedisClient.create(vertx, RedisHelper.options(config)); 38 | Job.setVertx(vertx, RedisHelper.client(vertx, config)); // init static vertx instance inner job 39 | } 40 | 41 | @Override 42 | public JobService getJob(long id, Handler> handler) { 43 | String zid = RedisHelper.createFIFO(id); 44 | client.hgetall(RedisHelper.getKey("job:" + id), r -> { 45 | if (r.succeeded()) { 46 | try { 47 | if (!r.result().containsKey("id")) { 48 | handler.handle(Future.succeededFuture()); 49 | } else { 50 | Job job = new Job(r.result()); 51 | job.setId(id); 52 | job.setZid(zid); 53 | handler.handle(Future.succeededFuture(job)); 54 | } 55 | } catch (Exception e) { 56 | e.printStackTrace(); 57 | this.removeBadJob(id, "", null); 58 | handler.handle(Future.failedFuture(e)); 59 | } 60 | } else { 61 | this.removeBadJob(id, "", null); 62 | handler.handle(Future.failedFuture(r.cause())); 63 | } 64 | }); 65 | return this; 66 | } 67 | 68 | @Override 69 | public JobService removeJob(long id, Handler> handler) { 70 | this.getJob(id, r -> { 71 | if (r.succeeded()) { 72 | if (r.result() != null) { 73 | r.result().remove() 74 | .setHandler(handler); 75 | } else { 76 | handler.handle(Future.succeededFuture()); 77 | } 78 | } else { 79 | handler.handle(Future.failedFuture(r.cause())); 80 | } 81 | }); 82 | return this; 83 | } 84 | 85 | @Override 86 | public JobService existsJob(long id, Handler> handler) { 87 | client.exists(RedisHelper.getKey("job:" + id), r -> { 88 | if (r.succeeded()) { 89 | if (r.result() == 0) 90 | handler.handle(Future.succeededFuture(false)); 91 | else 92 | handler.handle(Future.succeededFuture(true)); 93 | } else { 94 | handler.handle(Future.failedFuture(r.cause())); 95 | } 96 | }); 97 | return this; 98 | } 99 | 100 | @Override 101 | public JobService getJobLog(long id, Handler> handler) { 102 | client.lrange(RedisHelper.getKey("job:" + id + ":log"), 0, -1, handler); 103 | return this; 104 | } 105 | 106 | @Override 107 | public JobService jobRangeByState(String state, long from, long to, String order, Handler>> handler) { 108 | return rangeGeneral("jobs:" + state.toUpperCase(), from, to, order, handler); 109 | } 110 | 111 | @Override 112 | public JobService jobRangeByType(String type, String state, long from, long to, String order, Handler>> handler) { 113 | return rangeGeneral("jobs:" + type + ":" + state.toUpperCase(), from, to, order, handler); 114 | } 115 | 116 | @Override 117 | public JobService jobRange(long from, long to, String order, Handler>> handler) { 118 | return rangeGeneral("jobs", from, to, order, handler); 119 | } 120 | 121 | /** 122 | * Range job by from, to and order 123 | * 124 | * @param key range type(key) 125 | * @param from from 126 | * @param to to 127 | * @param order range order(asc, desc) 128 | * @param handler result handler 129 | */ 130 | private JobService rangeGeneral(String key, long from, long to, String order, Handler>> handler) { 131 | if (to < from) { 132 | handler.handle(Future.failedFuture("to can not be greater than from")); 133 | return this; 134 | } 135 | client.zrange(RedisHelper.getKey(key), from, to, r -> { 136 | if (r.succeeded()) { 137 | if (r.result().size() == 0) { // maybe empty 138 | handler.handle(Future.succeededFuture(new ArrayList<>())); 139 | } else { 140 | List list = (List) r.result().getList().stream() 141 | .map(e -> RedisHelper.numStripFIFO((String) e)) 142 | .collect(Collectors.toList()); 143 | list.sort((a1, a2) -> { 144 | if (order.equals("asc")) 145 | return Long.compare(a1, a2); 146 | else 147 | return Long.compare(a2, a1); 148 | }); 149 | long max = Math.max(list.get(0), list.get(list.size() - 1)); 150 | List jobList = new ArrayList<>(); 151 | list.forEach(e -> { 152 | this.getJob(e, jr -> { 153 | if (jr.succeeded()) { 154 | if (jr.result() != null) { 155 | jobList.add(jr.result()); 156 | } 157 | if (e >= max) { 158 | handler.handle(Future.succeededFuture(jobList)); 159 | } 160 | } else { 161 | handler.handle(Future.failedFuture(jr.cause())); 162 | } 163 | }); 164 | }); 165 | } 166 | } else { 167 | handler.handle(Future.failedFuture(r.cause())); 168 | } 169 | }); 170 | return this; 171 | } 172 | 173 | /** 174 | * Remove bad job by id (absolutely) 175 | * 176 | * @param id job id 177 | * @param handler result handler 178 | */ 179 | private JobService removeBadJob(long id, String jobType, Handler> handler) { 180 | String zid = RedisHelper.createFIFO(id); 181 | client.transaction().multi(null) 182 | .del(RedisHelper.getKey("job:" + id + ":log"), null) 183 | .del(RedisHelper.getKey("job:" + id), null) 184 | .zrem(RedisHelper.getKey("jobs:INACTIVE"), zid, null) 185 | .zrem(RedisHelper.getKey("jobs:ACTIVE"), zid, null) 186 | .zrem(RedisHelper.getKey("jobs:COMPLETE"), zid, null) 187 | .zrem(RedisHelper.getKey("jobs:FAILED"), zid, null) 188 | .zrem(RedisHelper.getKey("jobs:DELAYED"), zid, null) 189 | .zrem(RedisHelper.getKey("jobs"), zid, null) 190 | .zrem(RedisHelper.getKey("jobs:" + jobType + ":INACTIVE"), zid, null) 191 | .zrem(RedisHelper.getKey("jobs:" + jobType + ":ACTIVE"), zid, null) 192 | .zrem(RedisHelper.getKey("jobs:" + jobType + ":COMPLETE"), zid, null) 193 | .zrem(RedisHelper.getKey("jobs:" + jobType + ":FAILED"), zid, null) 194 | .zrem(RedisHelper.getKey("jobs:" + jobType + ":DELAYED"), zid, null) 195 | .exec(r -> { 196 | if (handler != null) { 197 | if (r.succeeded()) 198 | handler.handle(Future.succeededFuture()); 199 | else 200 | handler.handle(Future.failedFuture(r.cause())); 201 | } 202 | }); 203 | 204 | // TODO: add search functionality 205 | 206 | return this; 207 | } 208 | 209 | @Override 210 | public JobService cardByType(String type, JobState state, Handler> handler) { 211 | client.zcard(RedisHelper.getKey("jobs:" + type + ":" + state.name()), handler); 212 | return this; 213 | } 214 | 215 | @Override 216 | public JobService card(JobState state, Handler> handler) { 217 | client.zcard(RedisHelper.getKey("jobs:" + state.name()), handler); 218 | return this; 219 | } 220 | 221 | @Override 222 | public JobService completeCount(String type, Handler> handler) { 223 | if (type == null) 224 | return this.card(JobState.COMPLETE, handler); 225 | else 226 | return this.cardByType(type, JobState.COMPLETE, handler); 227 | } 228 | 229 | @Override 230 | public JobService failedCount(String type, Handler> handler) { 231 | if (type == null) 232 | return this.card(JobState.FAILED, handler); 233 | else 234 | return this.cardByType(type, JobState.FAILED, handler); 235 | } 236 | 237 | @Override 238 | public JobService inactiveCount(String type, Handler> handler) { 239 | if (type == null) 240 | return this.card(JobState.INACTIVE, handler); 241 | else 242 | return this.cardByType(type, JobState.INACTIVE, handler); 243 | } 244 | 245 | @Override 246 | public JobService activeCount(String type, Handler> handler) { 247 | if (type == null) 248 | return this.card(JobState.ACTIVE, handler); 249 | else 250 | return this.cardByType(type, JobState.ACTIVE, handler); 251 | } 252 | 253 | @Override 254 | public JobService delayedCount(String type, Handler> handler) { 255 | if (type == null) 256 | return this.card(JobState.DELAYED, handler); 257 | else 258 | return this.cardByType(type, JobState.DELAYED, handler); 259 | } 260 | 261 | @Override 262 | public JobService getAllTypes(Handler>> handler) { 263 | client.smembers(RedisHelper.getKey("job:types"), r -> { 264 | if (r.succeeded()) { 265 | handler.handle(Future.succeededFuture(r.result().getList())); 266 | } else { 267 | handler.handle(Future.failedFuture(r.cause())); 268 | } 269 | }); 270 | return this; 271 | } 272 | 273 | @Override 274 | public JobService getIdsByState(JobState state, Handler>> handler) { 275 | client.zrange(RedisHelper.getStateKey(state), 0, -1, r -> { 276 | if (r.succeeded()) { 277 | List list = r.result().stream() 278 | .map(e -> RedisHelper.numStripFIFO((String) e)) 279 | .collect(Collectors.toList()); 280 | handler.handle(Future.succeededFuture(list)); 281 | } else { 282 | handler.handle(Future.failedFuture(r.cause())); 283 | } 284 | }); 285 | return this; 286 | } 287 | 288 | @Override 289 | public JobService getWorkTime(Handler> handler) { 290 | client.get(RedisHelper.getKey("stats:work-time"), r -> { 291 | if (r.succeeded()) { 292 | handler.handle(Future.succeededFuture(Long.parseLong(r.result() == null ? "0" : r.result()))); 293 | } else { 294 | handler.handle(Future.failedFuture(r.cause())); 295 | } 296 | }); 297 | return this; 298 | } 299 | } 300 | -------------------------------------------------------------------------------- /kue-http/src/main/resources/webroot/public/stylesheets/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | padding: 50px 120px; 3 | } 4 | 5 | #menu { 6 | margin: 0; 7 | padding: 0; 8 | position: fixed; 9 | top: 0; 10 | left: 0; 11 | height: 100%; 12 | width: 80px; 13 | background: #3b3b3b; 14 | border-right: 1px solid #232323; 15 | -webkit-box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.5); 16 | box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.5); 17 | } 18 | 19 | #menu li { 20 | margin: 0; 21 | list-style: none; 22 | } 23 | 24 | #menu li { 25 | position: relative; 26 | text-align: center; 27 | } 28 | 29 | #menu li .count { 30 | position: absolute; 31 | top: 15px; 32 | left: 0; 33 | text-shadow: 1px 1px 1px #333; 34 | width: 100%; 35 | color: #808080; 36 | } 37 | 38 | #menu li a { 39 | display: block; 40 | padding: 40px 0 10px 0; 41 | color: #777; 42 | border-top: 1px solid #545454; 43 | border-bottom: 1px solid #333; 44 | font-size: 12px; 45 | background: #3a3a3a; 46 | } 47 | 48 | #menu li a:hover { 49 | background: #444; 50 | } 51 | 52 | #menu li a:active, 53 | #menu li a.active { 54 | background: #343434; 55 | -webkit-box-shadow: inset 0 0 3px 2px #292929, inset 0 -5px 10px 2px #313131; 56 | box-shadow: inset 0 0 3px 2px #292929, inset 0 -5px 10px 2px #313131; 57 | border-bottom: 1px solid #232323; 58 | } 59 | 60 | .context-menu { 61 | display: none; 62 | margin: 0; 63 | padding: 0; 64 | border: 1px solid #eee; 65 | border-bottom-color: rgba(0, 0, 0, 0.25); 66 | border-left-color: rgba(0, 0, 0, 0.2); 67 | border-right-color: rgba(0, 0, 0, 0.2); 68 | -webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1); 69 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1); 70 | -webkit-border-radius: 4px; 71 | border-radius: 4px; 72 | } 73 | 74 | .context-menu li { 75 | margin: 0; 76 | list-style: none; 77 | } 78 | 79 | .context-menu li:last-child a { 80 | border-bottom: none; 81 | } 82 | 83 | .context-menu li a { 84 | display: block; 85 | background: #fff; 86 | padding: 5px 10px; 87 | border: 1px solid transparent; 88 | border-bottom: 1px solid #eee; 89 | font-size: 12px; 90 | } 91 | 92 | .context-menu li a:hover { 93 | background: -webkit-linear-gradient(bottom, #00b3e9, #74dfff); 94 | background: -moz-linear-gradient(bottom, #00b3e9, #74dfff); 95 | background: -o-linear-gradient(bottom, #00b3e9, #74dfff); 96 | background: -ms-linear-gradient(bottom, #00b3e9, #74dfff); 97 | background: linear-gradient(to top, #00b3e9, #74dfff); 98 | color: #fff; 99 | border: 1px solid #fff; 100 | } 101 | 102 | .context-menu li a:active { 103 | background: -webkit-linear-gradient(bottom, #06c5ff, #83e2ff); 104 | background: -moz-linear-gradient(bottom, #06c5ff, #83e2ff); 105 | background: -o-linear-gradient(bottom, #06c5ff, #83e2ff); 106 | background: -ms-linear-gradient(bottom, #06c5ff, #83e2ff); 107 | background: linear-gradient(to top, #06c5ff, #83e2ff); 108 | } 109 | 110 | #job-template { 111 | display: none; 112 | } 113 | 114 | .block { 115 | border: 1px solid #eee; 116 | border-bottom-color: rgba(0, 0, 0, 0.25); 117 | border-left-color: rgba(0, 0, 0, 0.2); 118 | border-right-color: rgba(0, 0, 0, 0.2); 119 | -webkit-box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1); 120 | box-shadow: 0 2px 2px 0 rgba(0, 0, 0, 0.1); 121 | -webkit-border-radius: 4px; 122 | border-radius: 4px; 123 | width: 90%; 124 | margin: 10px 25px; 125 | padding: 20px 25px; 126 | } 127 | 128 | .block h2 { 129 | margin: 0; 130 | position: absolute; 131 | top: 5px; 132 | left: -15px; 133 | padding: 5px; 134 | font-size: 10px; 135 | -webkit-border-top-left-radius: 5px; 136 | border-top-left-radius: 5px; 137 | -webkit-border-bottom-left-radius: 5px; 138 | border-bottom-left-radius: 5px; 139 | -webkit-border-top-right-radius: 2px; 140 | border-top-right-radius: 2px; 141 | -webkit-border-bottom-right-radius: 2px; 142 | border-bottom-right-radius: 2px; 143 | background: -webkit-linear-gradient(left, #6b6b6b, #7e7e7e 50%); 144 | background: -moz-linear-gradient(left, #6b6b6b, #7e7e7e 50%); 145 | background: -o-linear-gradient(left, #6b6b6b, #7e7e7e 50%); 146 | background: -ms-linear-gradient(left, #6b6b6b, #7e7e7e 50%); 147 | background: linear-gradient(to right, #6b6b6b, #7e7e7e 50%); 148 | -webkit-box-shadow: -1px 0 1px 1px rgba(0, 0, 0, 0.1); 149 | box-shadow: -1px 0 1px 1px rgba(0, 0, 0, 0.1); 150 | color: #fff; 151 | text-shadow: 1px 1px 1px #444; 152 | } 153 | 154 | .block .type { 155 | color: #929292; 156 | } 157 | 158 | .job td.title em { 159 | color: #929292; 160 | } 161 | 162 | .job .block { 163 | position: relative; 164 | background: #fff; 165 | cursor: pointer; 166 | } 167 | 168 | .job .block table td:first-child { 169 | display: none; 170 | } 171 | 172 | .job .block .progress { 173 | position: absolute; 174 | top: 15px; 175 | right: 20px; 176 | } 177 | 178 | .job .block .attempts { 179 | display: none; 180 | position: absolute; 181 | top: 0; 182 | right: 0; 183 | padding: 5px 8px; 184 | -webkit-border-radius: 2px; 185 | border-radius: 2px; 186 | font-size: 10px; 187 | } 188 | 189 | .job .block .remove { 190 | position: absolute; 191 | top: 30px; 192 | right: -6px; 193 | /*background: white*/ 194 | background: #f05151; 195 | color: #fff; 196 | display: block; 197 | width: 20px; 198 | height: 20px; 199 | line-height: 20px; 200 | text-align: center; 201 | font-size: 12px; 202 | font-weight: bold; 203 | outline: none; 204 | border: 1px solid #eee; 205 | -webkit-border-radius: 20px; 206 | border-radius: 20px; 207 | -webkit-transition: opacity 200ms, top 300ms; 208 | -moz-transition: opacity 200ms, top 300ms; 209 | -o-transition: opacity 200ms, top 300ms; 210 | -ms-transition: opacity 200ms, top 300ms; 211 | transition: opacity 200ms, top 300ms; 212 | opacity: 0; 213 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 214 | filter: alpha(opacity=0); 215 | } 216 | 217 | .job .block .remove:hover { 218 | border: 1px solid #d6d6d6; 219 | } 220 | 221 | .job .block .remove:active { 222 | border: 1px solid #bebebe; 223 | } 224 | 225 | .job .block .restart { 226 | position: absolute; 227 | top: 30px; 228 | right: -6px; 229 | /*background: white*/ 230 | background: #00e600; 231 | color: #fff; 232 | display: block; 233 | width: 20px; 234 | height: 20px; 235 | line-height: 20px; 236 | text-align: center; 237 | font-size: 12px; 238 | font-weight: bold; 239 | outline: none; 240 | border: 1px solid #eee; 241 | -webkit-border-radius: 20px; 242 | border-radius: 20px; 243 | -webkit-transition: opacity 200ms, top 300ms; 244 | -moz-transition: opacity 200ms, top 300ms; 245 | -o-transition: opacity 200ms, top 300ms; 246 | -ms-transition: opacity 200ms, top 300ms; 247 | transition: opacity 200ms, top 300ms; 248 | opacity: 0; 249 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 250 | filter: alpha(opacity=0); 251 | } 252 | 253 | .job .block .restart:hover { 254 | border: 1px solid #d6d6d6; 255 | } 256 | 257 | .job .block .restart:active { 258 | border: 1px solid #bebebe; 259 | } 260 | 261 | .job .block:hover .remove { 262 | opacity: 1; 263 | -ms-filter: none; 264 | filter: none; 265 | top: -6px; 266 | } 267 | 268 | .job .block:hover .restart { 269 | opacity: 1; 270 | -ms-filter: none; 271 | filter: none; 272 | top: 16px; 273 | } 274 | 275 | .job .details { 276 | background: #3b3b3b; 277 | width: 89%; 278 | margin-top: -10px; 279 | margin-left: 35px; 280 | -webkit-border-bottom-left-radius: 5px; 281 | border-bottom-left-radius: 5px; 282 | -webkit-border-bottom-right-radius: 5px; 283 | border-bottom-right-radius: 5px; 284 | -webkit-box-shadow: inset 0 1px 10px 0 rgba(0, 0, 0, 0.8); 285 | box-shadow: inset 0 1px 10px 0 rgba(0, 0, 0, 0.8); 286 | -webkit-transition: padding 200ms, height 200ms; 287 | -moz-transition: padding 200ms, height 200ms; 288 | -o-transition: padding 200ms, height 200ms; 289 | -ms-transition: padding 200ms, height 200ms; 290 | transition: padding 200ms, height 200ms; 291 | height: 0; 292 | overflow: hidden; 293 | } 294 | 295 | .job .details table { 296 | width: 100%; 297 | } 298 | 299 | .job .details table td:first-child { 300 | width: 60px; 301 | color: #949494; 302 | } 303 | 304 | .job .details.show { 305 | padding: 15px 20px; 306 | height: auto; 307 | } 308 | 309 | .job ul.log { 310 | margin: 0; 311 | padding: 0; 312 | margin: 5px; 313 | padding: 10px; 314 | max-height: 100px; 315 | overflow-y: auto; 316 | -webkit-border-radius: 5px; 317 | border-radius: 5px; 318 | width: 95%; 319 | } 320 | 321 | .job ul.log li { 322 | margin: 0; 323 | list-style: none; 324 | } 325 | 326 | .job ul.log li { 327 | padding: 5px 0; 328 | border-bottom: 1px dotted #424242; 329 | color: #666; 330 | } 331 | 332 | .job ul.log li:last-child { 333 | border-bottom: none; 334 | } 335 | 336 | .job .details ::-webkit-scrollbar { 337 | width: 2px; 338 | } 339 | 340 | .job .details ::-webkit-scrollbar-thumb:vertical { 341 | background: #858585; 342 | } 343 | 344 | .job .details ::-webkit-scrollbar-track { 345 | border: 1px solid rgba(255, 255, 255, 0.1); 346 | } 347 | 348 | .job .details > div { 349 | padding: 10px 0; 350 | border-bottom: 1px solid #424242; 351 | } 352 | 353 | .job .details > div:last-child { 354 | border-bottom: none; 355 | } 356 | 357 | #actions { 358 | position: fixed; 359 | top: -2px; 360 | right: -2px; 361 | z-index: 20; 362 | } 363 | 364 | #sort, 365 | #filter, 366 | #search { 367 | float: left; 368 | margin: 0; 369 | padding: 5px 10px; 370 | border: 1px solid #eee; 371 | -webkit-border-radius: 0 0 0 5px; 372 | border-radius: 0 0 0 5px; 373 | -webkit-appearance: none; 374 | color: #3b3b3b; 375 | outline: none; 376 | } 377 | 378 | #sort:hover, 379 | #filter:hover, 380 | #search:hover { 381 | border-color: #d6d6d6; 382 | } 383 | 384 | #sort, 385 | #filter { 386 | cursor: pointer; 387 | } 388 | 389 | #sort, 390 | #filter { 391 | -webkit-border-radius: 0; 392 | border-radius: 0; 393 | border-left: none; 394 | } 395 | 396 | #error { 397 | position: fixed; 398 | top: -50px; 399 | right: 15px; 400 | padding: 20px; 401 | -webkit-transition: top 500ms, opacity 500ms; 402 | -moz-transition: top 500ms, opacity 500ms; 403 | -o-transition: top 500ms, opacity 500ms; 404 | -ms-transition: top 500ms, opacity 500ms; 405 | transition: top 500ms, opacity 500ms; 406 | opacity: 0; 407 | -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=0)"; 408 | filter: alpha(opacity=0); 409 | background: rgba(59, 59, 59, 0.2); 410 | border: 1px solid rgba(59, 59, 59, 0.3); 411 | -webkit-border-radius: 5px; 412 | border-radius: 5px; 413 | color: #3b3b3b; 414 | } 415 | 416 | #error.show { 417 | top: 15px; 418 | opacity: 1; 419 | -ms-filter: none; 420 | filter: none; 421 | } 422 | 423 | body { 424 | font: 13px "helvetica neue", helvetica, arial, sans-serif; 425 | -webkit-font-smoothing: antialiased; 426 | background: #fff; 427 | color: #666; 428 | } 429 | 430 | h1, 431 | h2, 432 | h3 { 433 | margin: 0 0 25px 0; 434 | padding: 0; 435 | font-weight: normal; 436 | text-transform: capitalize; 437 | color: #666; 438 | } 439 | 440 | h2 { 441 | font-size: 16px; 442 | margin-top: 20px; 443 | } 444 | 445 | pre { 446 | margin-top: 20px; 447 | } 448 | 449 | a { 450 | text-decoration: none; 451 | cursor: pointer; 452 | } 453 | 454 | table { 455 | border-collapse: separate; 456 | border-spacing: 0; 457 | vertical-align: middle; 458 | } 459 | 460 | table tr td { 461 | padding: 2px 5px; 462 | } 463 | 464 | #loading { 465 | width: 100%; 466 | text-align: center; 467 | margin-top: 40px; 468 | margin-left: 20px; 469 | } 470 | 471 | #loading canvas { 472 | margin: 0 auto; 473 | } 474 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /kue-core/src/main/generated/io/vertx/blueprint/kue/service/JobServiceVertxProxyHandler.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.blueprint.kue.service; 18 | 19 | import io.vertx.blueprint.kue.service.JobService; 20 | import io.vertx.core.Vertx; 21 | import io.vertx.core.Handler; 22 | import io.vertx.core.AsyncResult; 23 | import io.vertx.core.eventbus.EventBus; 24 | import io.vertx.core.eventbus.Message; 25 | import io.vertx.core.eventbus.MessageConsumer; 26 | import io.vertx.core.eventbus.DeliveryOptions; 27 | import io.vertx.core.eventbus.ReplyException; 28 | import io.vertx.core.json.JsonObject; 29 | import io.vertx.core.json.JsonArray; 30 | import java.util.Collection; 31 | import java.util.ArrayList; 32 | import java.util.HashSet; 33 | import java.util.List; 34 | import java.util.Map; 35 | import java.util.Set; 36 | import java.util.UUID; 37 | import java.util.stream.Collectors; 38 | import io.vertx.serviceproxy.ProxyHelper; 39 | import io.vertx.serviceproxy.ProxyHandler; 40 | import io.vertx.serviceproxy.ServiceException; 41 | import io.vertx.serviceproxy.ServiceExceptionMessageCodec; 42 | import io.vertx.blueprint.kue.service.JobService; 43 | import io.vertx.core.Vertx; 44 | import io.vertx.blueprint.kue.queue.JobState; 45 | import io.vertx.core.json.JsonArray; 46 | import java.util.List; 47 | import io.vertx.blueprint.kue.queue.Job; 48 | import io.vertx.core.json.JsonObject; 49 | import io.vertx.core.AsyncResult; 50 | import io.vertx.core.Handler; 51 | 52 | /* 53 | Generated Proxy code - DO NOT EDIT 54 | @author Roger the Robot 55 | */ 56 | @SuppressWarnings({"unchecked", "rawtypes"}) 57 | public class JobServiceVertxProxyHandler extends ProxyHandler { 58 | 59 | public static final long DEFAULT_CONNECTION_TIMEOUT = 5 * 60; // 5 minutes 60 | 61 | private final Vertx vertx; 62 | private final JobService service; 63 | private final long timerID; 64 | private long lastAccessed; 65 | private final long timeoutSeconds; 66 | 67 | public JobServiceVertxProxyHandler(Vertx vertx, JobService service) { 68 | this(vertx, service, DEFAULT_CONNECTION_TIMEOUT); 69 | } 70 | 71 | public JobServiceVertxProxyHandler(Vertx vertx, JobService service, long timeoutInSecond) { 72 | this(vertx, service, true, timeoutInSecond); 73 | } 74 | 75 | public JobServiceVertxProxyHandler(Vertx vertx, JobService service, boolean topLevel, long timeoutSeconds) { 76 | this.vertx = vertx; 77 | this.service = service; 78 | this.timeoutSeconds = timeoutSeconds; 79 | try { 80 | this.vertx.eventBus().registerDefaultCodec(ServiceException.class, 81 | new ServiceExceptionMessageCodec()); 82 | } catch (IllegalStateException ex) { 83 | } 84 | if (timeoutSeconds != -1 && !topLevel) { 85 | long period = timeoutSeconds * 1000 / 2; 86 | if (period > 10000) { 87 | period = 10000; 88 | } 89 | this.timerID = vertx.setPeriodic(period, this::checkTimedOut); 90 | } else { 91 | this.timerID = -1; 92 | } 93 | accessed(); 94 | } 95 | 96 | public MessageConsumer registerHandler(String address) { 97 | MessageConsumer consumer = vertx.eventBus().consumer(address).handler(this); 98 | this.setConsumer(consumer); 99 | return consumer; 100 | } 101 | 102 | private void checkTimedOut(long id) { 103 | long now = System.nanoTime(); 104 | if (now - lastAccessed > timeoutSeconds * 1000000000) { 105 | close(); 106 | } 107 | } 108 | 109 | @Override 110 | public void close() { 111 | if (timerID != -1) { 112 | vertx.cancelTimer(timerID); 113 | } 114 | super.close(); 115 | } 116 | 117 | private void accessed() { 118 | this.lastAccessed = System.nanoTime(); 119 | } 120 | 121 | public void handle(Message msg) { 122 | try { 123 | JsonObject json = msg.body(); 124 | String action = msg.headers().get("action"); 125 | if (action == null) { 126 | throw new IllegalStateException("action not specified"); 127 | } 128 | accessed(); 129 | switch (action) { 130 | 131 | 132 | case "getJob": { 133 | service.getJob(json.getValue("id") == null ? null : (json.getLong("id").longValue()), res -> { 134 | if (res.failed()) { 135 | if (res.cause() instanceof ServiceException) { 136 | msg.reply(res.cause()); 137 | } else { 138 | msg.reply(new ServiceException(-1, res.cause().getMessage())); 139 | } 140 | } else { 141 | msg.reply(res.result() == null ? null : res.result().toJson()); 142 | } 143 | }); 144 | break; 145 | } 146 | case "removeJob": { 147 | service.removeJob(json.getValue("id") == null ? null : (json.getLong("id").longValue()), createHandler(msg)); 148 | break; 149 | } 150 | case "existsJob": { 151 | service.existsJob(json.getValue("id") == null ? null : (json.getLong("id").longValue()), createHandler(msg)); 152 | break; 153 | } 154 | case "getJobLog": { 155 | service.getJobLog(json.getValue("id") == null ? null : (json.getLong("id").longValue()), createHandler(msg)); 156 | break; 157 | } 158 | case "jobRangeByState": { 159 | service.jobRangeByState((java.lang.String) json.getValue("state"), json.getValue("from") == null ? null : (json.getLong("from").longValue()), json.getValue("to") == null ? null : (json.getLong("to").longValue()), (java.lang.String) json.getValue("order"), res -> { 160 | if (res.failed()) { 161 | if (res.cause() instanceof ServiceException) { 162 | msg.reply(res.cause()); 163 | } else { 164 | msg.reply(new ServiceException(-1, res.cause().getMessage())); 165 | } 166 | } else { 167 | msg.reply(new JsonArray(res.result().stream().map(Job::toJson).collect(Collectors.toList()))); 168 | } 169 | }); 170 | break; 171 | } 172 | case "jobRangeByType": { 173 | service.jobRangeByType((java.lang.String) json.getValue("type"), (java.lang.String) json.getValue("state"), json.getValue("from") == null ? null : (json.getLong("from").longValue()), json.getValue("to") == null ? null : (json.getLong("to").longValue()), (java.lang.String) json.getValue("order"), res -> { 174 | if (res.failed()) { 175 | if (res.cause() instanceof ServiceException) { 176 | msg.reply(res.cause()); 177 | } else { 178 | msg.reply(new ServiceException(-1, res.cause().getMessage())); 179 | } 180 | } else { 181 | msg.reply(new JsonArray(res.result().stream().map(Job::toJson).collect(Collectors.toList()))); 182 | } 183 | }); 184 | break; 185 | } 186 | case "jobRange": { 187 | service.jobRange(json.getValue("from") == null ? null : (json.getLong("from").longValue()), json.getValue("to") == null ? null : (json.getLong("to").longValue()), (java.lang.String) json.getValue("order"), res -> { 188 | if (res.failed()) { 189 | if (res.cause() instanceof ServiceException) { 190 | msg.reply(res.cause()); 191 | } else { 192 | msg.reply(new ServiceException(-1, res.cause().getMessage())); 193 | } 194 | } else { 195 | msg.reply(new JsonArray(res.result().stream().map(Job::toJson).collect(Collectors.toList()))); 196 | } 197 | }); 198 | break; 199 | } 200 | case "cardByType": { 201 | service.cardByType((java.lang.String) json.getValue("type"), json.getString("state") == null ? null : io.vertx.blueprint.kue.queue.JobState.valueOf(json.getString("state")), createHandler(msg)); 202 | break; 203 | } 204 | case "card": { 205 | service.card(json.getString("state") == null ? null : io.vertx.blueprint.kue.queue.JobState.valueOf(json.getString("state")), createHandler(msg)); 206 | break; 207 | } 208 | case "completeCount": { 209 | service.completeCount((java.lang.String) json.getValue("type"), createHandler(msg)); 210 | break; 211 | } 212 | case "failedCount": { 213 | service.failedCount((java.lang.String) json.getValue("type"), createHandler(msg)); 214 | break; 215 | } 216 | case "inactiveCount": { 217 | service.inactiveCount((java.lang.String) json.getValue("type"), createHandler(msg)); 218 | break; 219 | } 220 | case "activeCount": { 221 | service.activeCount((java.lang.String) json.getValue("type"), createHandler(msg)); 222 | break; 223 | } 224 | case "delayedCount": { 225 | service.delayedCount((java.lang.String) json.getValue("type"), createHandler(msg)); 226 | break; 227 | } 228 | case "getAllTypes": { 229 | service.getAllTypes(createListHandler(msg)); 230 | break; 231 | } 232 | case "getIdsByState": { 233 | service.getIdsByState(json.getString("state") == null ? null : io.vertx.blueprint.kue.queue.JobState.valueOf(json.getString("state")), createListHandler(msg)); 234 | break; 235 | } 236 | case "getWorkTime": { 237 | service.getWorkTime(createHandler(msg)); 238 | break; 239 | } 240 | default: { 241 | throw new IllegalStateException("Invalid action: " + action); 242 | } 243 | } 244 | } catch (Throwable t) { 245 | msg.reply(new ServiceException(500, t.getMessage())); 246 | throw t; 247 | } 248 | } 249 | 250 | private Handler> createHandler(Message msg) { 251 | return res -> { 252 | if (res.failed()) { 253 | if (res.cause() instanceof ServiceException) { 254 | msg.reply(res.cause()); 255 | } else { 256 | msg.reply(new ServiceException(-1, res.cause().getMessage())); 257 | } 258 | } else { 259 | if (res.result() != null && res.result().getClass().isEnum()) { 260 | msg.reply(((Enum) res.result()).name()); 261 | } else { 262 | msg.reply(res.result()); 263 | } 264 | } 265 | }; 266 | } 267 | 268 | private Handler>> createListHandler(Message msg) { 269 | return res -> { 270 | if (res.failed()) { 271 | if (res.cause() instanceof ServiceException) { 272 | msg.reply(res.cause()); 273 | } else { 274 | msg.reply(new ServiceException(-1, res.cause().getMessage())); 275 | } 276 | } else { 277 | msg.reply(new JsonArray(res.result())); 278 | } 279 | }; 280 | } 281 | 282 | private Handler>> createSetHandler(Message msg) { 283 | return res -> { 284 | if (res.failed()) { 285 | if (res.cause() instanceof ServiceException) { 286 | msg.reply(res.cause()); 287 | } else { 288 | msg.reply(new ServiceException(-1, res.cause().getMessage())); 289 | } 290 | } else { 291 | msg.reply(new JsonArray(new ArrayList<>(res.result()))); 292 | } 293 | }; 294 | } 295 | 296 | private Handler>> createListCharHandler(Message msg) { 297 | return res -> { 298 | if (res.failed()) { 299 | if (res.cause() instanceof ServiceException) { 300 | msg.reply(res.cause()); 301 | } else { 302 | msg.reply(new ServiceException(-1, res.cause().getMessage())); 303 | } 304 | } else { 305 | JsonArray arr = new JsonArray(); 306 | for (Character chr : res.result()) { 307 | arr.add((int) chr); 308 | } 309 | msg.reply(arr); 310 | } 311 | }; 312 | } 313 | 314 | private Handler>> createSetCharHandler(Message msg) { 315 | return res -> { 316 | if (res.failed()) { 317 | if (res.cause() instanceof ServiceException) { 318 | msg.reply(res.cause()); 319 | } else { 320 | msg.reply(new ServiceException(-1, res.cause().getMessage())); 321 | } 322 | } else { 323 | JsonArray arr = new JsonArray(); 324 | for (Character chr : res.result()) { 325 | arr.add((int) chr); 326 | } 327 | msg.reply(arr); 328 | } 329 | }; 330 | } 331 | 332 | private Map convertMap(Map map) { 333 | return (Map) map; 334 | } 335 | 336 | private List convertList(List list) { 337 | return (List) list; 338 | } 339 | 340 | private Set convertSet(List list) { 341 | return new HashSet((List) list); 342 | } 343 | } -------------------------------------------------------------------------------- /kue-core/src/main/java/io/vertx/blueprint/kue/Kue.java: -------------------------------------------------------------------------------- 1 | package io.vertx.blueprint.kue; 2 | 3 | import io.vertx.blueprint.kue.queue.Job; 4 | import io.vertx.blueprint.kue.queue.JobState; 5 | import io.vertx.blueprint.kue.queue.KueWorker; 6 | import io.vertx.blueprint.kue.service.JobService; 7 | import io.vertx.blueprint.kue.util.RedisHelper; 8 | 9 | import io.vertx.core.AsyncResult; 10 | import io.vertx.core.DeploymentOptions; 11 | import io.vertx.core.Future; 12 | import io.vertx.core.Handler; 13 | import io.vertx.core.Vertx; 14 | import io.vertx.core.eventbus.Message; 15 | import io.vertx.core.json.JsonArray; 16 | import io.vertx.core.json.JsonObject; 17 | import io.vertx.redis.RedisClient; 18 | import io.vertx.redis.op.RangeLimitOptions; 19 | 20 | import java.util.List; 21 | import java.util.Optional; 22 | 23 | import static io.vertx.blueprint.kue.queue.KueVerticle.EB_JOB_SERVICE_ADDRESS; 24 | 25 | /** 26 | * The Kue class refers to a job queue. 27 | * 28 | * @author Eric Zhao 29 | */ 30 | public class Kue { 31 | 32 | private final JsonObject config; 33 | private final Vertx vertx; 34 | private final JobService jobService; 35 | private final RedisClient client; 36 | 37 | public Kue(Vertx vertx, JsonObject config) { 38 | this.vertx = vertx; 39 | this.config = config; 40 | this.jobService = JobService.createProxy(vertx, EB_JOB_SERVICE_ADDRESS); 41 | this.client = RedisHelper.client(vertx, config); 42 | Job.setVertx(vertx, RedisHelper.client(vertx, config)); // init static vertx instance inner job 43 | } 44 | 45 | /** 46 | * Generate handler address with certain job on event bus. 47 | *

    Format: vertx.kue.handler.job.{handlerType}.{addressId}.{jobType}

    48 | * 49 | * @return corresponding address 50 | */ 51 | public static String getCertainJobAddress(String handlerType, Job job) { 52 | return "vertx.kue.handler.job." + handlerType + "." + job.getAddress_id() + "." + job.getType(); 53 | } 54 | 55 | /** 56 | * Generate worker address on event bus. 57 | *

    Format: vertx.kue.handler.workers.{eventType}

    58 | * 59 | * @return corresponding address 60 | */ 61 | public static String workerAddress(String eventType) { 62 | return "vertx.kue.handler.workers." + eventType; 63 | } 64 | 65 | /** 66 | * Generate worker address on event bus. 67 | *

    Format: vertx.kue.handler.workers.{eventType}.{addressId}

    68 | * 69 | * @return corresponding address 70 | */ 71 | public static String workerAddress(String eventType, Job job) { 72 | return "vertx.kue.handler.workers." + eventType + "." + job.getAddress_id(); 73 | } 74 | 75 | /** 76 | * Create a Kue instance. 77 | * 78 | * @param vertx vertx instance 79 | * @param config config json object 80 | * @return kue instance 81 | */ 82 | public static Kue createQueue(Vertx vertx, JsonObject config) { 83 | return new Kue(vertx, config); 84 | } 85 | 86 | /** 87 | * Get the JobService. 88 | * Notice: only available in package scope 89 | */ 90 | JobService getJobService() { 91 | return this.jobService; 92 | } 93 | 94 | /** 95 | * Create a job instance. 96 | * 97 | * @param type job type 98 | * @param data job extra data 99 | * @return a new job instance 100 | */ 101 | public Job createJob(String type, JsonObject data) { 102 | return new Job(type, data); 103 | } 104 | 105 | private void processInternal(String type, Handler handler, boolean isWorker) { 106 | KueWorker worker = new KueWorker(type, handler, this); 107 | vertx.deployVerticle(worker, new DeploymentOptions().setWorker(isWorker), r0 -> { 108 | if (r0.succeeded()) { 109 | this.on("job_complete", msg -> { 110 | long dur = new Job(((JsonObject) msg.body()).getJsonObject("job")).getDuration(); 111 | client.incrby(RedisHelper.getKey("stats:work-time"), dur, r1 -> { 112 | if (r1.failed()) 113 | r1.cause().printStackTrace(); 114 | }); 115 | }); 116 | } 117 | }); 118 | } 119 | 120 | /** 121 | * Queue-level events listener. 122 | * 123 | * @param eventType event type 124 | * @param handler handler 125 | */ 126 | public Kue on(String eventType, Handler> handler) { 127 | vertx.eventBus().consumer(Kue.workerAddress(eventType), handler); 128 | return this; 129 | } 130 | 131 | /** 132 | * Process a job in asynchronous way. 133 | * 134 | * @param type job type 135 | * @param n job process times 136 | * @param handler job process handler 137 | */ 138 | public Kue process(String type, int n, Handler handler) { 139 | if (n <= 0) { 140 | throw new IllegalStateException("The process times must be positive"); 141 | } 142 | while (n-- > 0) { 143 | processInternal(type, handler, false); 144 | } 145 | setupTimers(); 146 | return this; 147 | } 148 | 149 | /** 150 | * Process a job in asynchronous way (once). 151 | * 152 | * @param type job type 153 | * @param handler job process handler 154 | */ 155 | public Kue process(String type, Handler handler) { 156 | processInternal(type, handler, false); 157 | setupTimers(); 158 | return this; 159 | } 160 | 161 | /** 162 | * Process a job that may be blocking. 163 | * 164 | * @param type job type 165 | * @param n job process times 166 | * @param handler job process handler 167 | */ 168 | public Kue processBlocking(String type, int n, Handler handler) { 169 | if (n <= 0) { 170 | throw new IllegalStateException("The process times must be positive"); 171 | } 172 | while (n-- > 0) { 173 | processInternal(type, handler, true); 174 | } 175 | setupTimers(); 176 | return this; 177 | } 178 | 179 | // job logic 180 | 181 | /** 182 | * Get job from backend by id. 183 | * 184 | * @param id job id 185 | * @return async result 186 | */ 187 | public Future> getJob(long id) { 188 | Future> future = Future.future(); 189 | jobService.getJob(id, r -> { 190 | if (r.succeeded()) { 191 | future.complete(Optional.ofNullable(r.result())); 192 | } else { 193 | future.fail(r.cause()); 194 | } 195 | }); 196 | return future; 197 | } 198 | 199 | /** 200 | * Remove a job by id. 201 | * 202 | * @param id job id 203 | * @return async result 204 | */ 205 | public Future removeJob(long id) { 206 | return this.getJob(id).compose(r -> { 207 | if (r.isPresent()) { 208 | return r.get().remove(); 209 | } else { 210 | return Future.succeededFuture(); 211 | } 212 | }); 213 | } 214 | 215 | /** 216 | * Judge whether a job with certain id exists. 217 | * 218 | * @param id job id 219 | * @return async result 220 | */ 221 | public Future existsJob(long id) { 222 | Future future = Future.future(); 223 | jobService.existsJob(id, future.completer()); 224 | return future; 225 | } 226 | 227 | /** 228 | * Get job log by id. 229 | * 230 | * @param id job id 231 | * @return async result 232 | */ 233 | public Future getJobLog(long id) { 234 | Future future = Future.future(); 235 | jobService.getJobLog(id, future.completer()); 236 | return future; 237 | } 238 | 239 | /** 240 | * Get a list of job in certain state in range (from, to) with order. 241 | * 242 | * @return async result 243 | * @see JobService#jobRangeByState(String, long, long, String, Handler) 244 | */ 245 | public Future> jobRangeByState(String state, long from, long to, String order) { 246 | Future> future = Future.future(); 247 | jobService.jobRangeByState(state, from, to, order, future.completer()); 248 | return future; 249 | } 250 | 251 | /** 252 | * Get a list of job in certain state and type in range (from, to) with order. 253 | * 254 | * @return async result 255 | * @see JobService#jobRangeByType(String, String, long, long, String, Handler) 256 | */ 257 | public Future> jobRangeByType(String type, String state, long from, long to, String order) { 258 | Future> future = Future.future(); 259 | jobService.jobRangeByType(type, state, from, to, order, future.completer()); 260 | return future; 261 | } 262 | 263 | /** 264 | * Get a list of job in range (from, to) with order. 265 | * 266 | * @return async result 267 | * @see JobService#jobRange(long, long, String, Handler) 268 | */ 269 | public Future> jobRange(long from, long to, String order) { 270 | Future> future = Future.future(); 271 | jobService.jobRange(from, to, order, future.completer()); 272 | return future; 273 | } 274 | 275 | // runtime cardinality metrics 276 | 277 | /** 278 | * Get cardinality by job type and state. 279 | * 280 | * @param type job type 281 | * @param state job state 282 | * @return corresponding cardinality (Future) 283 | */ 284 | public Future cardByType(String type, JobState state) { 285 | Future future = Future.future(); 286 | jobService.cardByType(type, state, future.completer()); 287 | return future; 288 | } 289 | 290 | /** 291 | * Get cardinality by job state. 292 | * 293 | * @param state job state 294 | * @return corresponding cardinality (Future) 295 | */ 296 | public Future card(JobState state) { 297 | Future future = Future.future(); 298 | jobService.card(state, future.completer()); 299 | return future; 300 | } 301 | 302 | /** 303 | * Get cardinality of completed jobs. 304 | * 305 | * @param type job type; if null, then return global metrics. 306 | */ 307 | public Future completeCount(String type) { 308 | Future future = Future.future(); 309 | jobService.completeCount(type, future.completer()); 310 | return future; 311 | } 312 | 313 | /** 314 | * Get cardinality of failed jobs. 315 | * 316 | * @param type job type; if null, then return global metrics. 317 | */ 318 | public Future failedCount(String type) { 319 | Future future = Future.future(); 320 | jobService.failedCount(type, future.completer()); 321 | return future; 322 | } 323 | 324 | /** 325 | * Get cardinality of inactive jobs. 326 | * 327 | * @param type job type; if null, then return global metrics. 328 | */ 329 | public Future inactiveCount(String type) { 330 | Future future = Future.future(); 331 | jobService.inactiveCount(type, future.completer()); 332 | return future; 333 | } 334 | 335 | /** 336 | * Get cardinality of active jobs. 337 | * 338 | * @param type job type; if null, then return global metrics. 339 | */ 340 | public Future activeCount(String type) { 341 | Future future = Future.future(); 342 | jobService.activeCount(type, future.completer()); 343 | return future; 344 | } 345 | 346 | /** 347 | * Get cardinality of delayed jobs. 348 | * 349 | * @param type job type; if null, then return global metrics. 350 | */ 351 | public Future delayedCount(String type) { 352 | Future future = Future.future(); 353 | jobService.delayedCount(type, future.completer()); 354 | return future; 355 | } 356 | 357 | /** 358 | * Get the job types present. 359 | * 360 | * @return async result list 361 | */ 362 | public Future> getAllTypes() { 363 | Future> future = Future.future(); 364 | jobService.getAllTypes(future.completer()); 365 | return future; 366 | } 367 | 368 | /** 369 | * Return job ids with the given `state`. 370 | * 371 | * @param state job state 372 | * @return async result list 373 | */ 374 | public Future> getIdsByState(JobState state) { 375 | Future> future = Future.future(); 376 | jobService.getIdsByState(state, future.completer()); 377 | return future; 378 | } 379 | 380 | /** 381 | * Get queue work time in milliseconds. 382 | * 383 | * @return async result 384 | */ 385 | public Future getWorkTime() { 386 | Future future = Future.future(); 387 | jobService.getWorkTime(future.completer()); 388 | return future; 389 | } 390 | 391 | /** 392 | * Set up timers for checking job promotion and active job ttl. 393 | */ 394 | private void setupTimers() { 395 | this.checkJobPromotion(); 396 | this.checkActiveJobTtl(); 397 | } 398 | 399 | /** 400 | * Check job promotion. 401 | * Promote delayed jobs, checking every `ms`. 402 | */ 403 | private void checkJobPromotion() { // TODO: TO REVIEW 404 | int timeout = config.getInteger("job.promotion.interval", 1000); 405 | int limit = config.getInteger("job.promotion.limit", 1000); 406 | // need a mechanism to stop the circuit timer 407 | vertx.setPeriodic(timeout, l -> { 408 | client.zrangebyscore(RedisHelper.getKey("jobs:DELAYED"), String.valueOf(0), String.valueOf(System.currentTimeMillis()), 409 | new RangeLimitOptions(new JsonObject().put("offset", 0).put("count", limit)), r -> { 410 | if (r.succeeded()) { 411 | r.result().forEach(r1 -> { 412 | long id = Long.parseLong(RedisHelper.stripFIFO((String) r1)); 413 | this.getJob(id).compose(jr -> jr.get().inactive()) 414 | .setHandler(jr -> { 415 | if (jr.succeeded()) { 416 | jr.result().emit("promotion", jr.result().getId()); 417 | } else { 418 | jr.cause().printStackTrace(); 419 | } 420 | }); 421 | }); 422 | } else { 423 | r.cause().printStackTrace(); 424 | } 425 | }); 426 | }); 427 | } 428 | 429 | /** 430 | * Check active job ttl. 431 | */ 432 | private void checkActiveJobTtl() { // TODO 433 | int timeout = config.getInteger("job.ttl.interval", 1000); 434 | int limit = config.getInteger("job.ttl.limit", 1000); 435 | // need a mechanism to stop the circuit timer 436 | vertx.setPeriodic(timeout, l -> { 437 | client.zrangebyscore(RedisHelper.getKey("jobs:ACTIVE"), String.valueOf(100000), String.valueOf(System.currentTimeMillis()), 438 | new RangeLimitOptions(new JsonObject().put("offset", 0).put("count", limit)), r -> { 439 | 440 | }); 441 | }); 442 | } 443 | 444 | } 445 | -------------------------------------------------------------------------------- /kue-core/src/main/generated/io/vertx/blueprint/kue/service/rxjava/JobService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Red Hat, Inc. 3 | * 4 | * Red Hat licenses this file to you under the Apache License, version 2.0 5 | * (the "License"); you may not use this file except in compliance with the 6 | * License. You may obtain a copy of the License at: 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | * License for the specific language governing permissions and limitations 14 | * under the License. 15 | */ 16 | 17 | package io.vertx.blueprint.kue.service.rxjava; 18 | 19 | import java.util.Map; 20 | import rx.Observable; 21 | import rx.Single; 22 | import io.vertx.rxjava.core.Vertx; 23 | import io.vertx.blueprint.kue.queue.JobState; 24 | import io.vertx.core.json.JsonArray; 25 | import java.util.List; 26 | import io.vertx.blueprint.kue.queue.Job; 27 | import io.vertx.core.json.JsonObject; 28 | import io.vertx.core.AsyncResult; 29 | import io.vertx.core.Handler; 30 | 31 | /** 32 | * Service interface for task operations. 33 | * 34 | *

    35 | * NOTE: This class has been automatically generated from the {@link io.vertx.blueprint.kue.service.JobService original} non RX-ified interface using Vert.x codegen. 36 | */ 37 | 38 | @io.vertx.lang.rxjava.RxGen(io.vertx.blueprint.kue.service.JobService.class) 39 | public class JobService { 40 | 41 | public static final io.vertx.lang.rxjava.TypeArg __TYPE_ARG = new io.vertx.lang.rxjava.TypeArg<>( 42 | obj -> new JobService((io.vertx.blueprint.kue.service.JobService) obj), 43 | JobService::getDelegate 44 | ); 45 | 46 | private final io.vertx.blueprint.kue.service.JobService delegate; 47 | 48 | public JobService(io.vertx.blueprint.kue.service.JobService delegate) { 49 | this.delegate = delegate; 50 | } 51 | 52 | public io.vertx.blueprint.kue.service.JobService getDelegate() { 53 | return delegate; 54 | } 55 | 56 | /** 57 | * Factory method for creating a {@link io.vertx.blueprint.kue.service.rxjava.JobService} instance. 58 | * 59 | * @param vertx Vertx instance 60 | * @param config configuration 61 | * @return the new {@link io.vertx.blueprint.kue.service.rxjava.JobService} instance 62 | */ 63 | public static JobService create(Vertx vertx, JsonObject config) { 64 | JobService ret = JobService.newInstance(io.vertx.blueprint.kue.service.JobService.create(vertx.getDelegate(), config)); 65 | return ret; 66 | } 67 | 68 | /** 69 | * Factory method for creating a {@link io.vertx.blueprint.kue.service.rxjava.JobService} service proxy. 70 | * This is useful for doing RPCs. 71 | * 72 | * @param vertx Vertx instance 73 | * @param address event bus address of RPC 74 | * @return the new {@link io.vertx.blueprint.kue.service.rxjava.JobService} service proxy 75 | */ 76 | public static JobService createProxy(Vertx vertx, String address) { 77 | JobService ret = JobService.newInstance(io.vertx.blueprint.kue.service.JobService.createProxy(vertx.getDelegate(), address)); 78 | return ret; 79 | } 80 | 81 | /** 82 | * Get the certain from backend by id. 83 | * @param id job id 84 | * @param handler async result handler 85 | * @return 86 | */ 87 | public JobService getJob(long id, Handler> handler) { 88 | delegate.getJob(id, handler); 89 | return this; 90 | } 91 | 92 | /** 93 | * Get the certain from backend by id. 94 | * @param id job id 95 | * @return 96 | */ 97 | public Single rxGetJob(long id) { 98 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 99 | getJob(id, fut); 100 | })); 101 | } 102 | 103 | /** 104 | * Remove a job by id. 105 | * @param id job id 106 | * @param handler async result handler 107 | * @return 108 | */ 109 | public JobService removeJob(long id, Handler> handler) { 110 | delegate.removeJob(id, handler); 111 | return this; 112 | } 113 | 114 | /** 115 | * Remove a job by id. 116 | * @param id job id 117 | * @return 118 | */ 119 | public Single rxRemoveJob(long id) { 120 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 121 | removeJob(id, fut); 122 | })); 123 | } 124 | 125 | /** 126 | * Judge whether a job with certain id exists. 127 | * @param id job id 128 | * @param handler async result handler 129 | * @return 130 | */ 131 | public JobService existsJob(long id, Handler> handler) { 132 | delegate.existsJob(id, handler); 133 | return this; 134 | } 135 | 136 | /** 137 | * Judge whether a job with certain id exists. 138 | * @param id job id 139 | * @return 140 | */ 141 | public Single rxExistsJob(long id) { 142 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 143 | existsJob(id, fut); 144 | })); 145 | } 146 | 147 | /** 148 | * Get job log by id. 149 | * @param id job id 150 | * @param handler async result handler 151 | * @return 152 | */ 153 | public JobService getJobLog(long id, Handler> handler) { 154 | delegate.getJobLog(id, handler); 155 | return this; 156 | } 157 | 158 | /** 159 | * Get job log by id. 160 | * @param id job id 161 | * @return 162 | */ 163 | public Single rxGetJobLog(long id) { 164 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 165 | getJobLog(id, fut); 166 | })); 167 | } 168 | 169 | /** 170 | * Get a list of job in certain state in range (from, to) with order. 171 | * @param state expected job state 172 | * @param from from 173 | * @param to to 174 | * @param order range order 175 | * @param handler async result handler 176 | * @return 177 | */ 178 | public JobService jobRangeByState(String state, long from, long to, String order, Handler>> handler) { 179 | delegate.jobRangeByState(state, from, to, order, handler); 180 | return this; 181 | } 182 | 183 | /** 184 | * Get a list of job in certain state in range (from, to) with order. 185 | * @param state expected job state 186 | * @param from from 187 | * @param to to 188 | * @param order range order 189 | * @return 190 | */ 191 | public Single> rxJobRangeByState(String state, long from, long to, String order) { 192 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 193 | jobRangeByState(state, from, to, order, fut); 194 | })); 195 | } 196 | 197 | /** 198 | * Get a list of job in certain state and type in range (from, to) with order. 199 | * @param type expected job type 200 | * @param state expected job state 201 | * @param from from 202 | * @param to to 203 | * @param order range order 204 | * @param handler async result handler 205 | * @return 206 | */ 207 | public JobService jobRangeByType(String type, String state, long from, long to, String order, Handler>> handler) { 208 | delegate.jobRangeByType(type, state, from, to, order, handler); 209 | return this; 210 | } 211 | 212 | /** 213 | * Get a list of job in certain state and type in range (from, to) with order. 214 | * @param type expected job type 215 | * @param state expected job state 216 | * @param from from 217 | * @param to to 218 | * @param order range order 219 | * @return 220 | */ 221 | public Single> rxJobRangeByType(String type, String state, long from, long to, String order) { 222 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 223 | jobRangeByType(type, state, from, to, order, fut); 224 | })); 225 | } 226 | 227 | /** 228 | * Get a list of job in range (from, to) with order. 229 | * @param from from 230 | * @param to to 231 | * @param order range order 232 | * @param handler async result handler 233 | * @return 234 | */ 235 | public JobService jobRange(long from, long to, String order, Handler>> handler) { 236 | delegate.jobRange(from, to, order, handler); 237 | return this; 238 | } 239 | 240 | /** 241 | * Get a list of job in range (from, to) with order. 242 | * @param from from 243 | * @param to to 244 | * @param order range order 245 | * @return 246 | */ 247 | public Single> rxJobRange(long from, long to, String order) { 248 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 249 | jobRange(from, to, order, fut); 250 | })); 251 | } 252 | 253 | /** 254 | * Get cardinality by job type and state. 255 | * @param type job type 256 | * @param state job state 257 | * @param handler async result handler 258 | * @return 259 | */ 260 | public JobService cardByType(String type, JobState state, Handler> handler) { 261 | delegate.cardByType(type, state, handler); 262 | return this; 263 | } 264 | 265 | /** 266 | * Get cardinality by job type and state. 267 | * @param type job type 268 | * @param state job state 269 | * @return 270 | */ 271 | public Single rxCardByType(String type, JobState state) { 272 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 273 | cardByType(type, state, fut); 274 | })); 275 | } 276 | 277 | /** 278 | * Get cardinality by job state. 279 | * @param state job state 280 | * @param handler async result handler 281 | * @return 282 | */ 283 | public JobService card(JobState state, Handler> handler) { 284 | delegate.card(state, handler); 285 | return this; 286 | } 287 | 288 | /** 289 | * Get cardinality by job state. 290 | * @param state job state 291 | * @return 292 | */ 293 | public Single rxCard(JobState state) { 294 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 295 | card(state, fut); 296 | })); 297 | } 298 | 299 | /** 300 | * Get cardinality of completed jobs. 301 | * @param type job type; if null, then return global metrics 302 | * @param handler async result handler 303 | * @return 304 | */ 305 | public JobService completeCount(String type, Handler> handler) { 306 | delegate.completeCount(type, handler); 307 | return this; 308 | } 309 | 310 | /** 311 | * Get cardinality of completed jobs. 312 | * @param type job type; if null, then return global metrics 313 | * @return 314 | */ 315 | public Single rxCompleteCount(String type) { 316 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 317 | completeCount(type, fut); 318 | })); 319 | } 320 | 321 | /** 322 | * Get cardinality of failed jobs. 323 | * @param type job type; if null, then return global metrics 324 | * @param handler 325 | * @return 326 | */ 327 | public JobService failedCount(String type, Handler> handler) { 328 | delegate.failedCount(type, handler); 329 | return this; 330 | } 331 | 332 | /** 333 | * Get cardinality of failed jobs. 334 | * @param type job type; if null, then return global metrics 335 | * @return 336 | */ 337 | public Single rxFailedCount(String type) { 338 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 339 | failedCount(type, fut); 340 | })); 341 | } 342 | 343 | /** 344 | * Get cardinality of inactive jobs. 345 | * @param type job type; if null, then return global metrics 346 | * @param handler 347 | * @return 348 | */ 349 | public JobService inactiveCount(String type, Handler> handler) { 350 | delegate.inactiveCount(type, handler); 351 | return this; 352 | } 353 | 354 | /** 355 | * Get cardinality of inactive jobs. 356 | * @param type job type; if null, then return global metrics 357 | * @return 358 | */ 359 | public Single rxInactiveCount(String type) { 360 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 361 | inactiveCount(type, fut); 362 | })); 363 | } 364 | 365 | /** 366 | * Get cardinality of active jobs. 367 | * @param type job type; if null, then return global metrics 368 | * @param handler 369 | * @return 370 | */ 371 | public JobService activeCount(String type, Handler> handler) { 372 | delegate.activeCount(type, handler); 373 | return this; 374 | } 375 | 376 | /** 377 | * Get cardinality of active jobs. 378 | * @param type job type; if null, then return global metrics 379 | * @return 380 | */ 381 | public Single rxActiveCount(String type) { 382 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 383 | activeCount(type, fut); 384 | })); 385 | } 386 | 387 | /** 388 | * Get cardinality of delayed jobs. 389 | * @param type job type; if null, then return global metrics 390 | * @param handler 391 | * @return 392 | */ 393 | public JobService delayedCount(String type, Handler> handler) { 394 | delegate.delayedCount(type, handler); 395 | return this; 396 | } 397 | 398 | /** 399 | * Get cardinality of delayed jobs. 400 | * @param type job type; if null, then return global metrics 401 | * @return 402 | */ 403 | public Single rxDelayedCount(String type) { 404 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 405 | delayedCount(type, fut); 406 | })); 407 | } 408 | 409 | /** 410 | * Get the job types present. 411 | * @param handler async result handler 412 | * @return 413 | */ 414 | public JobService getAllTypes(Handler>> handler) { 415 | delegate.getAllTypes(handler); 416 | return this; 417 | } 418 | 419 | /** 420 | * Get the job types present. 421 | * @return 422 | */ 423 | public Single> rxGetAllTypes() { 424 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 425 | getAllTypes(fut); 426 | })); 427 | } 428 | 429 | /** 430 | * Return job ids with the given . 431 | * @param state job state 432 | * @param handler async result handler 433 | * @return 434 | */ 435 | public JobService getIdsByState(JobState state, Handler>> handler) { 436 | delegate.getIdsByState(state, handler); 437 | return this; 438 | } 439 | 440 | /** 441 | * Return job ids with the given . 442 | * @param state job state 443 | * @return 444 | */ 445 | public Single> rxGetIdsByState(JobState state) { 446 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 447 | getIdsByState(state, fut); 448 | })); 449 | } 450 | 451 | /** 452 | * Get queue work time in milliseconds. 453 | * @param handler async result handler 454 | * @return 455 | */ 456 | public JobService getWorkTime(Handler> handler) { 457 | delegate.getWorkTime(handler); 458 | return this; 459 | } 460 | 461 | /** 462 | * Get queue work time in milliseconds. 463 | * @return 464 | */ 465 | public Single rxGetWorkTime() { 466 | return Single.create(new io.vertx.rx.java.SingleOnSubscribeAdapter<>(fut -> { 467 | getWorkTime(fut); 468 | })); 469 | } 470 | 471 | 472 | public static JobService newInstance(io.vertx.blueprint.kue.service.JobService arg) { 473 | return arg != null ? new JobService(arg) : null; 474 | } 475 | } 476 | --------------------------------------------------------------------------------