I have google cloud pubsub subscriber application(question is not related to pubsub, i believe there is same behaviour with jms listener). It don't require any webcontainer like tomcat or other(There is plenty questions where problem was missing container dependency, here i don`t need webcontainer). It should start and process messages. The problem: it stops after start.
Application is very simple, here is build.gradle file:
buildscript {
ext {
springBootVersion = '1.5.8.RELEASE'
}
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'org.springframework.boot'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenLocal()
mavenCentral()
}
dependencies {
compile 'org.springframework.boot:spring-boot-starter'
}
And here is application launcher:
#SpringBootApplication
public class CommunicationApplication {
public static void main(String[] args) throws IOException {
SpringApplication.run(CommunicationApplication.class, args);
//waiting data from system input to prevent stop after start
//System.in.read();
}
}
And here is pubsub manager bean(it creates pubsub listener)
#Component
public class PubsubManager {
private final Logger logger = LoggerFactory.getLogger(getClass());
#Autowired
private MessageReceiver messageReceiver;
private Subscriber subscriber;
#PostConstruct
protected void start() throws Exception {
createTopic();
createSubscriber();
}
private void createTopic() throws Exception {
TopicName topic = TopicName.of(ServiceOptions.getDefaultProjectId(), "COMMUNICATION_TOPIC_NAME");
try (TopicAdminClient topicAdminClient = TopicAdminClient.create()) {
topicAdminClient.createTopic(topic);
logger.info("Topic {}:{} created", topic.getProject(), topic.getTopic());
} catch (ApiException e) {
if (e.getStatusCode().getCode() == StatusCode.Code.ALREADY_EXISTS) {
logger.info("Topic {}:{} already exist", topic.getProject(), topic.getTopic());
} else {
logger.error("Error create topic: {}", e.getStatusCode().getCode());
}
logger.info("isRetryable: {}", e.isRetryable());
}
}
private void createSubscriber() throws Exception {
SubscriptionName subscriptionName = SubscriptionName.of(ServiceOptions.getDefaultProjectId(),
MessagingConstants.COMMUNICATION_SUBSCRIPTION_NAME);
// create a subscriber bound to the asynchronous message receiver
try (SubscriptionAdminClient subscriptionAdminClient = SubscriptionAdminClient.create()) {
TopicName topicName = TopicName.of(ServiceOptions.getDefaultProjectId(),
"COMMUNICATION_TOPIC_NAME");
Subscription subscription = subscriptionAdminClient.createSubscription(
subscriptionName, topicName, PushConfig.getDefaultInstance(), 0);
} catch (Exception e) {
logger.info("subscription already created:", e);
}
subscriber = Subscriber.newBuilder(subscriptionName, messageReceiver).build();
subscriber.startAsync().awaitRunning();
logger.info("subscriber start receiving messages");
}
#PreDestroy
protected void close() throws Exception {
logger.info("subscriber stop receiving messages");
subscriber.stopAsync();
}
}
If i uncomment System.in.read(); - it works at local environment, but at cloud it don`t work. Could you suggest proper way to fix this? Thanks.
If you want to create only the PubSub Listener, the following worked for me:
git clone https://github.com/spring-guides/gs-messaging-gcp-pubsub
cd gs-messaging-gcp-pubsub/complete
Change your build.gradle file to the build.gradle file below.
cd gs-messaging-gcp-pubsub/complete/src/main/resources Once in there you can delete the static folder.
Go to gs-messaging-gcp-pubsub/complete/src/main/java/hello ; In there you should have two files: PubSubApplication.java and WebAppController.java. Remove WebAppController.java and change the PubSubApplication.java to the file below:
build.gradle:
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.5.9.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
jar {
baseName = 'gs-spring-cloud-gcp'
version = '0.1.0'
}
repositories {
mavenCentral()
maven {
url "http://repo.spring.io/snapshot"
}
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.cloud:spring-cloud-gcp-starter-pubsub:1.0.0.BUILD-SNAPSHOT")
compile("org.springframework.cloud:spring-integration-gcp:1.0.0.BUILD-SNAPSHOT")
}
pubSubApplication.java
package hello;
import java.io.IOException;
import com.google.cloud.pubsub.v1.AckReplyConsumer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.gcp.pubsub.core.PubSubOperations;
import org.springframework.cloud.gcp.pubsub.core.PubSubTemplate;
import org.springframework.cloud.gcp.pubsub.support.GcpHeaders;
import org.springframework.context.annotation.Bean;
import org.springframework.integration.annotation.MessagingGateway;
import org.springframework.integration.annotation.ServiceActivator;
import org.springframework.integration.channel.DirectChannel;
import org.springframework.integration.gcp.pubsub.AckMode;
import org.springframework.integration.gcp.pubsub.inbound.PubSubInboundChannelAdapter;
import org.springframework.integration.gcp.pubsub.outbound.PubSubMessageHandler;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.MessageHandler;
#SpringBootApplication
public class PubSubApplication {
private static final Log LOGGER = LogFactory.getLog(PubSubApplication.class);
public static void main(String[] args) throws IOException {
SpringApplication.run(PubSubApplication.class, args);
}
#Bean
public MessageChannel pubsubInputChannel() {
return new DirectChannel();
}
#Bean
public PubSubInboundChannelAdapter messageChannelAdapter(
#Qualifier("pubsubInputChannel") MessageChannel inputChannel,
PubSubOperations pubSubTemplate) {
PubSubInboundChannelAdapter adapter =
new PubSubInboundChannelAdapter(pubSubTemplate, "testSubscription");
adapter.setOutputChannel(inputChannel);
adapter.setAckMode(AckMode.MANUAL);
return adapter;
}
#Bean
#ServiceActivator(inputChannel = "pubsubInputChannel")
public MessageHandler messageReceiver() {
return message -> {
LOGGER.info("Message arrived! Payload: " + message.getPayload());
AckReplyConsumer consumer =
(AckReplyConsumer) message.getHeaders().get(GcpHeaders.ACKNOWLEDGEMENT);
consumer.ack();
};
}
}
Change "testSubscription" to the Subscription you are using
Now in folder gs-messaging-gcp-pubsub/complete, if you run ./gradlew bootRun the app hello.PubSubApplication should get running on local. Any message you publish to the Topic where you are subscribed should appear where you are running the app.
If you have problems with credentials/authenticaion change the parameters in:
gs-messaging-gcp-pubsub/complete/src/main/resources/application.properties
Related
I keep getting this error:
Caused by: org.springframework.context.ApplicationContextException: Unable to start ServletWebServerApplicationContext due to missing ServletWebServerFactory bean.
This is the target in my run configuration.
package mystuff;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.builder.SpringApplicationBuilder;
#SpringBootApplication(scanBasePackages = "mystuff")
public class Runner {
public static void main(String[] args) {
new SpringApplicationBuilder(MyApp.class).run(args);
}
}
And this is MyApp.class:
package mystuff;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class MyApp {
#RequestMapping("/")
public String index() {
return "Hello World";
}
}
And this is my Gradle build file:
plugins {
java
id("org.springframework.boot") version "2.2.2.RELEASE"
id("io.spring.dependency-management") version "1.0.8.RELEASE"
}
group = "org.example"
version = "1.0-SNAPSHOT"
repositories {
mavenCentral()
}
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
testCompile("junit", "junit", "4.12")
}
configure<JavaPluginConvention> {
sourceCompatibility = JavaVersion.VERSION_1_8
}
Both MyApp.java and Runner.java are in the same package, "mystuff". I searched around here on Stackoverflow, but my configuration appears to be correct.
Could you maybe try to to
#SpringBootApplication(scanBasePackages = "mystuff")
public class Runner {
public static void main(String[] args) {
SpringApplication.run(Runner.class, args);
}
}
In Runner class, replace
new SpringApplicationBuilder(MyApp.class).run(args);
with
new SpringApplicationBuilder(MyApp.class).web(WebApplicationType.SERVLET).run(args);
.build() method should be called before .run()
please try below code
new SpringApplicationBuilder(MyApp.class).build().run(args);
I am trying to use spring-boot and gradle to create a simple CRUD application as a PoC. At this point, I am getting this error when I am attempting to load the front website-
2019-02-11 14:54:48.915 WARN 5704 --- [nio-8080-exec-3] o.s.web.servlet.PageNotFound : No mapping for GET /WEB-INF/jsp/welcome.jsp
I am using spring boot and gradle by specifying in the controller class, and the application properties and build gradle script but still I get this debug statement when I go to my welcome.jsp and the now corresponding 404 error.
I've already looked through a lot of tutorials, but a lot of them are in maven. At this point, I know I need to specify the path in the controller and specify the resources in the properties and build scripts. I have even tried creating a specific mvcconfiguration but I don't think I need one.
build.gradle
buildscript {
ext {
springBootVersion = '2.1.2.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}}
apply plugin: 'eclipse'
apply plugin: 'java'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'com.bill'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
}
ext {
set('springCloudServicesVersion', '2.1.0.RELEASE')
set('springCloudVersion', 'Greenwich.RELEASE')
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-actuator'
/*implementation 'io.pivotal.spring.cloud:spring-cloud-services-starter-config-client'
/*implementation 'io.pivotal.spring.cloud:spring-cloud-services-starter-service-registry'
implementation 'org.springframework.cloud:spring-cloud-starter'
/*implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-client'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-eureka-server'
implementation 'org.springframework.cloud:spring-cloud-starter-netflix-ribbon'
*/
runtimeOnly 'mysql:mysql-connector-java'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
dependencyManagement {
imports {
mavenBom "io.pivotal.spring.cloud:spring-cloud-services-dependencies:${springCloudServicesVersion}"
mavenBom "org.springframework.cloud:spring-cloud-dependencies:${springCloudVersion}"
}
}
application.properties
spring.mvc.view.prefix:/WEB-INF/jsp/
spring.mvc.view.suffix:.jsp
spring.jpa.hibernate.ddl-auto=update
spring.datasource.url=jdbc:mysql://localhost:3306/mh1
spring.datasource.username=root
spring.datasource.password=root
MainController.java
package com.javainuse;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.BindingResult;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
import com.javainuse.BankAccountApplication;
import com.javainuse.User;
import com.javainuse.UserDAO;
import com.javainuse.UserForm;
#Controller
public class MainController {
#Autowired
UserDAO userDAO;
#RequestMapping("/welcome")
public ModelAndView viewHome() {
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("welcome");
return modelAndView;
}
#RequestMapping("/users")
public String viewUsers(Model model) {
List<User> list = UserDAO.getUsers();
model.addAttribute("users", list);
return "usersPage";
}
#RequestMapping("/registerSuccessful")
public String viewRegisterSuccessful() {
return "registerSuccessfulPage";
}
#RequestMapping(value = "/register", method = RequestMethod.GET)
public String viewRegister(Model model) {
UserForm form = new UserForm();
model.addAttribute("userForm", form);
return "registerPage";
}
public String saveRegister( Model model, //
#ModelAttribute("userForm") #Validated UserForm userForm, //
BindingResult result, //
final RedirectAttributes redirectAttributes) {
// Validate result
if (result.hasErrors()) {
return "registerPage";
}
User newUser= null;
try {
newUser = userDAO.createUser(userForm);
}
// Other error!!
catch (Exception e) {
model.addAttribute("errorMessage", "Error: " + e.getMessage());
return "registerPage";
}
redirectAttributes.addFlashAttribute("newUser", newUser);
return "redirect:/registerSuccessful";
}
}
MvcConfiguration.java
package com.javainuse;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import org.springframework.web.servlet.view.InternalResourceViewResolver;
#Configuration
#ComponentScan(basePackages="...package name...")
#EnableWebMvc
public class MvcConfiguration extends WebMvcConfigurerAdapter{
#Bean
public InternalResourceViewResolver viewResolver() {
InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
viewResolver.setPrefix("WEB-INF/jsp/");
viewResolver.setSuffix(".jsp");
return viewResolver;
}
}
2019-02-11 14:54:48.915 WARN 5704 --- [nio-8080-exec-3] o.s.web.servlet.PageNotFound : No mapping for GET /WEB-INF/jsp/welcome.jsp
I'm attempting to create basic Axon/Spring application by tutorial, but faced with strange error like: NoHandlerForCommandException: No handler was subscribed to command. It seems that Axon can't see #CommandHandler annotation.
Here my files:
aggregate
#Aggregate
#NoArgsConstructor
public class Account {
#AggregateIdentifier
private UUID accountId;
private Double balance;
#CommandHandler
public Account(CreateAccountCommand command) {
apply(new AccountCreatedEvent(command.getAccountId(), command.getName()));
}
#EventSourcingHandler
protected void on(AccountCreatedEvent event) {
this.accountId = event.getAccountId();
this.balance = 0.0;
}
}
event
#AllArgsConstructor
#Getter
public class AccountCreatedEvent {
private UUID accountId;
private String name;
}
command
#Getter
public class CreateAccountCommand {
#TargetAggregateIdentifier
private UUID accountId;
private String name;
public CreateAccountCommand(UUID accountId, String name) {
this.accountId = accountId;
this.name = name;
}
}
spring boot conf
#SpringBootApplication
public class App {
#Configuration
public static class TestConfiguration {
#Bean
public EventStorageEngine inMemoryEventStorageEngine() {
return new InMemoryEventStorageEngine();
}
}
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
this is how i send cmd
#Component
public class AppLoader {
#Autowired
private CommandGateway cmdGateway;
#PostConstruct
public void init() {
cmdGateway.send(new CreateAccountCommand(UUID.randomUUID(), "test"));
}
}
my build.gradle
buildscript {
ext {
springBootVersion = '2.0.5.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'
group = 'io.yourpoint.nettnews'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
repositories {
mavenCentral()
}
dependencies {
testCompile group: 'org.axonframework', name: 'axon-test', version: '3.3.5'
compile group: 'org.axonframework', name: 'axon-spring-boot-starter', version: '3.3.5'
compile('org.springframework.boot:spring-boot-starter-webflux')
compileOnly('org.projectlombok:lombok')
testCompile('org.springframework.boot:spring-boot-starter-test')
testCompile('io.projectreactor:reactor-test')
}
It is very likely that performing the CommandGateway#send() call in a #PostConstruct is to early for Axon..
As it currently stands, all the command, event and query handlers typically get registered after all your beans have been initialized.
Moving the CommandGateway#send() behind a REST endpoint should give you a big enough time frame to ensure all the handlers have been registered.
So in short, it's a timing issues why you get the NoHandlerForCommandException.
We (at AxonIQ) see benefit in sending some sort of application events as soon as all the handlers have been registered. That's a thing for the future though.
Hope this helps you out #Benjamin!
Problem: Rule(InterestRate.drl) getting fired in standalone java code and giving me accurate result
package com.test.drool.config;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import com.test.drool.facts.InterestRate;
public class RunSampleInterestRate {
public static void main(String[] args) {
// TODO Auto-generated method stub
KieContainer container= KieServices.Factory.get().getKieClasspathContainer();
InterestRate interestRate=new InterestRate();
interestRate.setLender("RBL");
System.out.println("printing session object before inserting"+interestRate.toString());
KieSession kieSession=container.newKieSession("ksession-rules");
kieSession.insert(interestRate);
kieSession.fireAllRules();
System.out.println(interestRate.getRate());
}
}
gives me expected 12.5 as interest rate.
Problem:I have to integrate this in rest service and I have been trying to test same logic under rest environment and it is not giving me expected results.After firing rules,service
always returns default value 0.0.My Environment is Spring-boot and drool is 6.5.0 final.
POJO:
package com.test.drool.facts;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
#JsonIgnoreProperties(ignoreUnknown=true)
public class InterestRate {
#Override
public String toString() {
return "InterestRate [lender=" + lender + ", principal=" + principal + ", store=" + store
+ ", dealer=" + dealer + ", rate=" + rate + "]";
}
private String lender;
private String principal;
private String store;
private String dealer;
private double rate;
public double getRate() {
return rate;
}
public void setRate(double rate) {
this.rate = rate;
}
public String getLender() {
return lender;
}
public void setLender(String lender) {
this.lender = lender;
}
public String getPrincipal() {
return principal;
}
public void setPrincipal(String principal) {
this.principal = principal;
}
public String getStore() {
return store;
}
public void setStore(String store) {
this.store = store;
}
public String getDealer() {
return dealer;
}
public void setDealer(String dealer) {
this.dealer = dealer;
}
}
Bean Config
package com.test.drool.config;
import org.kie.api.KieServices;
import org.kie.api.runtime.KieContainer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class DroolDependencyConfig {
#Bean(name="kieContainer")
public KieContainer kieContainer() {
return KieServices.Factory.get().getKieClasspathContainer();
}
}
Controller:
package com.test.drool.controllers;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.test.drool.facts.InterestRate;
import com.test.drool.service.RuleExecuteService;
#RestController
#RequestMapping(value="/rule")
public class RuleExecuteController {
#Autowired
private RuleExecuteService executeService;
private static Logger logger=LoggerFactory.getLogger(RuleExecuteController.class);
#PostMapping(value = "/execute", consumes = MediaType.APPLICATION_JSON_VALUE,produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Double> getInterestRate(#RequestBody InterestRate interestRate){
logger.info(String.format("logging Request Object %s",interestRate.toString()));
return new ResponseEntity<Double>(executeService.executeRule(interestRate),HttpStatus.OK);
}
}
RuleServiceImpl:
package com.test.drool.service.impl;
import org.kie.api.runtime.KieContainer;
import org.kie.api.runtime.KieSession;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.test.drool.facts.InterestRate;
import com.test.drool.service.RuleExecuteService;
#Service
public class RuleExecutorServiceImpl implements RuleExecuteService {
private KieContainer kieContainer;
private static org.slf4j.Logger logger=LoggerFactory.getLogger(RuleExecutorServiceImpl.class);
#Autowired
public RuleExecutorServiceImpl(KieContainer kieContainer) {
this.kieContainer=kieContainer;
}
#Override
public double executeRule(InterestRate interestRate) {
logger.info("firing up session and executing rules");
KieSession kieSession= kieContainer.newKieSession("ksession-rules");
logger.info("Printing object before inserting in session"+interestRate.toString());
kieSession.insert(interestRate);
kieSession.fireAllRules();
System.out.println("returning values from rule execution"+">>>"+interestRate.getRate());
return interestRate.getRate();
}
}
DRL file:
package com.test.drool.facts
rule "Interest Rate"
when
$interestrate := InterestRate(lender.equals("RBL"))
then
$interestrate.setRate(12.30);
end
Gradle dependency:
dependencies {
compile "org.kie:kie-spring:${droolsVersion}"
compile "org.kie:kie-api:${droolsVersion}"
compile "org.drools:drools-core:${droolsVersion}"
compile "org.drools:drools-compiler:${droolsVersion}"
//compile('org.springframework.cloud:spring-cloud-starter-eureka')
compile('org.springframework.boot:spring-boot-starter-actuator')
compile ('org.springframework.boot:spring-boot-starter-web')
compile ('org.springframework.boot:spring-boot-starter-data-rest')
compile ('org.springframework.boot:spring-boot-devtools')
testCompile('org.springframework.boot:spring-boot-starter-test')
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.2'
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.0'
}
I don't know the reason why it works but we kept minimum spring drool in our gradle file.All me and my colleague did was to have only kie-spring and drool-compiler in gradle and it worked like a charm.We also need to exclude some modules of spring since spring boot 1.5 does not work with spring 3.2 on which kie depends.For future spring boot drool development,here is gradle. Just paste it and build and code as you will code for any other spring-boot project.
repositories {
mavenCentral()
maven {
name 'jboss'
url 'http://repository.jboss.org/nexus/content/groups/public-jboss'
}
}
buildscript {
ext {
springBootVersion = '1.5.3.RELEASE'
}
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
version = '0.0.1'
sourceCompatibility = 1.8
targetCompatibility = 1.8
repositories {
mavenCentral()
}
dependencyManagement {
imports {
mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Camden.SR6'
}
}
dependencies {
compile ('org.kie:kie-spring:6.5.0.Final'){
exclude group:'org.springframework', module: 'spring-core'
exclude group:'org.springframework', module: 'spring-tx'
exclude group:'org.springframework', module: 'spring-beans'
exclude group:'org.springframework', module: 'spring-context'
}
compile "org.drools:drools-compiler:6.5.0.Final"
compile ('org.springframework.boot:spring-boot-starter-web')
compile group: 'org.apache.commons', name: 'commons-lang3', version: '3.0'
}
task createFolder{
def configDir = new File(project.buildDir.path+"/libs", "config")
def keystoreDir = new File(project.buildDir.path+"/libs", "keystore")
def logDir = new File(project.buildDir.path+"/libs", "log")
def libDir = new File(project.buildDir.path+"/libs", "lib")
if(!logDir.exists()){
logDir.mkdirs()
}
delete configDir
delete libDir
delete keystoreDir
libDir.mkdirs()
configDir.mkdirs()
keystoreDir.mkdirs()
}
//copy config
task copyConfig(type: Copy) {
into project.buildDir.path+"/libs/config"
from "config"
}
//copy keystore
task copyKeystore(type: Copy) {
into project.buildDir.path+"/libs/keystore"
from "keystore"
}
//copy dependencies
task copyRuntimeLibs(type: Copy) {
into project.buildDir.path+"/libs/lib"
from configurations.compile
}
task bundleAll(type: Jar){
dependsOn 'createFolder', 'copyRuntimeLibs', 'copyConfig', 'copyKeystore'
manifest {
def manifestClasspath = configurations.compile.collect { "lib/" + it.getName() }.join(' ')
attributes 'Implementation-Title': 'rule-service',
'Implementation-Version': version,
'Main-Class': 'com.test.drool.starte.RuleStarter',
'Class-Path': manifestClasspath
}
baseName=project.name
from { (configurations.compile - configurations.compile).collect { it.isDirectory() ? it : zipTree(it) } }
with jar
exclude 'META-INF/*.RSA', 'META-INF/*.SF','META-INF/*.DSA'
}
task zip(type: Zip){
dependsOn 'bundleAll'
from 'build/libs'
}
Investigating Springfox and Swagger UI, but I am facing an issue. I am using the Spring Boot REST example project as the foundation for my PoC. I'm running JDK 8 and the project leverages Gradle.
First, here are the file contents for the project:
build.gradle
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath("org.springframework.boot:spring-boot-gradle-plugin:1.2.7.RELEASE")
}
}
apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'idea'
apply plugin: 'spring-boot'
jar {
baseName = 'gs-rest-service'
version = '0.1.0'
}
repositories {
mavenCentral()
jcenter()
}
sourceCompatibility = 1.8
targetCompatibility = 1.8
dependencies {
compile("org.springframework.boot:spring-boot-starter-web")
compile("io.springfox:springfox-swagger2:2.2.2")
compile("io.springfox:springfox-swagger-ui:2.2.2")
testCompile("junit:junit")
}
task wrapper(type: Wrapper) {
gradleVersion = '2.3'
}
GreetingController.java
package hello;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
#RequestMapping("/greeting")
public Greeting greeting(#RequestParam(value="name", defaultValue="World") String name) {
return new Greeting(counter.incrementAndGet(),
String.format(template, name));
}
}
Greeting.java
package hello;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
Application.java
package hello;
import static com.google.common.collect.Lists.newArrayList;
import static springfox.documentation.schema.AlternateTypeRules.newRule;
import java.time.LocalDate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.request.async.DeferredResult;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.builders.ResponseMessageBuilder;
import springfox.documentation.schema.ModelRef;
import springfox.documentation.schema.WildcardType;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
import com.fasterxml.classmate.TypeResolver;
#SpringBootApplication
#EnableSwagger2
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Autowired
private TypeResolver typeResolver;
#Bean
public Docket greetingApi() {
return new Docket(DocumentationType.SPRING_WEB)
.select()
.apis(RequestHandlerSelectors.any())
.paths(PathSelectors.any())
.build()
.pathMapping("/")
.directModelSubstitute(LocalDate.class, String.class)
.genericModelSubstitutes(ResponseEntity.class)
.alternateTypeRules(newRule(typeResolver.resolve(DeferredResult.class,
typeResolver.resolve(ResponseEntity.class, WildcardType.class)),
typeResolver.resolve(WildcardType.class)))
.useDefaultResponseMessages(false)
.globalResponseMessage(RequestMethod.GET,
newArrayList(new ResponseMessageBuilder()
.code(500)
.message("500 message")
.responseModel(new ModelRef("Error"))
.build()))
.enableUrlTemplating(true);
}
}
Here is the issue I am facing. When I build and run the application, I can successfully navigate to the Swagger UI page (http://localhost:8080/swagger-ui.html). When I expand greeting-controller, I see the different methods and expand "get /greeting{?name}". The Get section has the following content:
Response Class (Status 200)
Model
{
"content": "string",
"id": 0
}
Response Content Type: */*
Parameters
parameter = name, value = World, parameter type = query, data type = string
When I click the "Try It Out" button, I see the following:
curl = curl -X GET --header "Accept: */*" "http://localhost:8080/greeting{?name}?name=World"
request url = http://localhost:8080/greeting{?name}?name=World
repsonse body = {
"timestamp": 1446418006199,
"status": 404,
"error": "Not Found",
"message": "No message available",
"path": "/greeting%7B"
}
response code = 404
response headers = {
"server": "Apache-Coyote/1.1",
"content-type": "application/json;charset=UTF-8",
"transfer-encoding": "chunked",
"date": "Sun, 01 Nov 2015 22:46:46 GMT"
}
At first glance, it looks like for some reason that Springfox/Swagger is not correctly replacing the placeholder for {?name}. My question is, how do I configure it to do so, if that is in fact the issue, so that I can successfully test out the service from the Swagger UI page?
In your Application class changing the enableUrlTemplating to false will fix your problem.
#Bean
public Docket greetingApi() {
return new Docket(DocumentationType.SPRING_WEB)
//...
.enableUrlTemplating(false);
}
Just a little bit of background on that flag. That flag is to support RFC 6570 without which operations that differ only by query string parameters will not show up correctly per spec. In the next iteration of the swagger spec there are plans to address that issue. That is the reason for enableUrlTemplating to be marked as an incubating feature.