Swagger for Jersey API using embedded Grizzly - jersey

I'm pretty new to JAVA, and more specifically REST based services in JAVA.
I'm using Grizzly as an embedded web server, serving up a Jersey REST API. That's all working great, but when I try to add in Swagger to document the API, it doesn't work.
Here is my POM (using maven)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>swagger_test</groupId>
<artifactId>swagger_grizzly_test</artifactId>
<version>1.0-SNAPSHOT</version>
<!-- bring in all the jersey dependencies we need, from the same version -->
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.glassfish.jersey</groupId>
<artifactId>jersey-bom</artifactId>
<version>2.13</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<!-- the web server -->
<dependency>
<groupId>org.glassfish.jersey.containers</groupId>
<artifactId>jersey-container-grizzly2-http</artifactId>
</dependency>
<!-- json serializer -->
<dependency>
<groupId>org.glassfish.jersey.media</groupId>
<artifactId>jersey-media-json-jackson</artifactId>
<version>2.10.1</version>
</dependency>
<!-- jersey for API documentation -->
<dependency>
<groupId>com.wordnik</groupId>
<artifactId>swagger-jersey-jaxrs_2.10</artifactId>
<version>1.3.12</version>
</dependency>
</dependencies>
</project>
And here is my Main function, launching the server. Note my 'Browse' resource is under the package 'resources'.
public class Main
{
public static void main(String [ ] args)
{
String restUrl = "http://localhost:8080";
// Grizzly makes you add the resources you want to expose
final ResourceConfig rc = new ResourceConfig().packages ("resources", "com.wordnik.swagger.jersey.listing");
HttpServer server = null;
try
{
server = GrizzlyHttpServerFactory.createHttpServer(URI.create (restUrl), rc);
server.start();
System.out.println("Started...");
}
catch (Exception e)
{
System.out.println("Failed to start (" + e.toString () + ")");
}
// Wait for the user to close out of the app
try{System.in.read();} catch (IOException e) {}
if(server != null)
{
server.shutdownNow ();
}
}
}
Lastly, here is my one and only resource.
#Path("browse")
#Api(value = "/browse", description = "Browse tags")
public class Browse
{
#GET
#Produces(MediaType.APPLICATION_JSON)
#ApiOperation(value = "Browse for tags", notes = "Returns all tags in a flat list")
#ApiResponses(value = {
#ApiResponse(code = 200, message = "OK"),
#ApiResponse(code = 500, message = "Something wrong in Server")})
public String browse ()
{
return "Hello World";
}
}
If I go to http://localhost:8080/api-docs I get...
{
apiVersion: "1.0.0",
swaggerVersion: "1.2"
}
Note there are no APIs listed. I've followed a number of tutorials, but I'm not using servlets (directly) so I think this is a little different?
Any help would be awesome!

Download the source code here https://github.com/SingleMalt/jersey2-grizzly2-swagger-demo, match it, and it should work. I got it working now.
My biggest hurdle is that I'm loading the grizzly server from a JAR file. For some reason Jersey can't find the resources (including swagger), from the package names and I need to call rc.register(Browse.class); directly for each class.
This forced me to add the following from the "com.wordnik.swagger.jersey.listing" package to get things working.
// Required to support Swagger
rc.register(JerseyApiDeclarationProvider.class);
rc.register(JerseyResourceListingProvider.class);
rc.register(ApiListingResourceJSON.class);

Related

Spring Boot Spark on K8S (Minikube): cannot assign instance of java.lang.invoke.SerializedLambda

I've seen others have been dealing with this same issue, but since none of the proposed solutions or workarounds worked for me and I've already spent hours on this, I figured I would share my specific case in detail in hope someone could point out what I'm missing.
I wanted to experiment with running a very simple Spark Spring-Boot application on a Minikube k8s cluster. When I run the app locally (using SparkSession.builder().master("local")) everything works as expected. However, when I deploy my app to minikube, I manage to get my driver pod to spin up the executor pods when the job is triggered, but then I get this exception on my executor pods:
ERROR Executor: Exception in task 0.1 in stage 0.0 (TID 1)
cannot assign instance of java.lang.invoke.SerializedLambda to field org.apache.spark.sql.execution.MapPartitionsExec.func of type scala.Function1 in instance of org.apache.spark.sql.execution.MapPartitionsExec
Here is my spring-boot app. For the sake of simplicity of sharing this, I kept all the logic on the controller:
WordcountController
#RestController
public class WordCountController implements Serializable {
#PostMapping("/wordcount")
public ResponseEntity<String> handleFileUpload(#RequestParam("file") MultipartFile file) throws IOException {
String hostIp;
try {
hostIp = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
throw new RuntimeException(e);
}
SparkConf conf = new SparkConf();
conf.setAppName("count.words.in.file")
.setMaster("k8s://https://kubernetes.default.svc:443")
.setJars(new String[]{"/app/wordcount.jar"})
.set("spark.driver.host", hostIp)
.set("spark.driver.port", "8080")
.set("spark.kubernetes.namespace", "default")
.set("spark.kubernetes.container.image", "spark:3.3.2h.1")
.set("spark.executor.cores", "2")
.set("spark.executor.memory", "1g")
.set("spark.kubernetes.authenticate.executor.serviceAccountName", "spark")
.set("spark.kubernetes.dynamicAllocation.deleteGracePeriod", "20")
.set("spark.cores.max", "4")
.set("spark.executor.instances", "2");
SparkSession spark = SparkSession.builder()
.config(conf)
.getOrCreate();
byte[] byteArray = file.getBytes();
String contents = new String(byteArray, StandardCharsets.UTF_8);
Dataset<String> text = spark.createDataset(Arrays.asList(contents), Encoders.STRING());
Dataset<String> wordsDataset = text.flatMap((FlatMapFunction<String, String>) line -> {
List<String> words = new ArrayList<>();
for (String word : line.split(" ")) {
words.add(word);
}
return words.iterator();
}, Encoders.STRING());
// Count the number of occurrences of each word
Dataset<Row> wordCounts = wordsDataset.groupBy("value")
.agg(count("*").as("count"))
.orderBy(desc("count"));
// Convert the word count results to a List of Rows
List<Row> wordCountsList = wordCounts.collectAsList();
StringBuilder resultStringBuffer = new StringBuilder();
// Build the final string representation
for (Row row : wordCountsList) {
resultStringBuffer.append(row.getString(0)).append(": ").append(row.getLong(1)).append("\n");
}
return ResponseEntity.ok(resultStringBuffer.toString());
}
Here is my maven pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.8</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>wordcount</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>wordcount</name>
<description>wordcount</description>
<properties>
<java.version>11</java.version>
<spark.version>3.3.2</spark.version>
<scala.version>2.12</scala.version>
</properties>
<dependencyManagement>
<dependencies>
<!--Spark java.lang.NoClassDefFoundError: org/codehaus/janino/InternalCompilerException-->
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>commons-compiler</artifactId>
<version>3.0.8</version>
</dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.0.8</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>commons-compiler</artifactId>
<version>3.0.8</version>
</dependency>
<dependency>
<groupId>org.codehaus.janino</groupId>
<artifactId>janino</artifactId>
<version>3.0.8</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-core_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency> <!-- Spark dependency -->
<groupId>org.apache.spark</groupId>
<artifactId>spark-sql_${scala.version}</artifactId>
<version>${spark.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.spark</groupId>
<artifactId>spark-kubernetes_${scala.version}</artifactId>
<version>${spark.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
and here is the Dockerfile I'm using to package my spring-boot application before deploying it to minikube:
# Use an existing image as the base image
FROM openjdk:11-jdk
# Set the working directory
WORKDIR /app
# Copy the compiled JAR file to the image
COPY target/wordcount-0.0.1-SNAPSHOT.jar /app/wordcount.jar
RUN useradd -u 185 sparkuser
# Set the entrypoint command to run the JAR file
ENTRYPOINT ["java", "-jar", "wordcount.jar"]
For the spark.kubernetes.container.image I built a docker image using the Dockerfile which is shipped with my local Spark bin (spark-3.3.2-bin-hadoop3 - same Spark version used by my spring-boot app) following these instructions and loaded it to minikube.
Here are some of the things I tried with no luck so far:
Share my app's jar with Spark using setJars(new String[]{"/app/wordcount.jar"}) as suggested here - this absolute file-path is where my app's jar lives on my driver image
use maven-shade-plugin as suggested here to change the way my app's jar distributes its dependencies - this resulted in a ClassNotFoundException: SparkSession exception on my driver pod.
Refactor my controller's code to not use lambda functions (didn't make a difference):
public static class SplitLine implements FlatMapFunction<String, String> {
#Override
public Iterator<String> call(String line) throws Exception {
List<String> words = new ArrayList<>();
for (String word : line.split(" ")) {
words.add(word);
}
return words.iterator();
}
...
Dataset<String> wordsDataset = text.flatMap(new SplitLine(), Encoders.STRING());
Any tips or hints regarding my setup or suggestions on how I can refactor my code to get it to work with the existing setup would be greatly appreciated.

Method not allowed error for GraphQL requests with Spring

I have a Java Spring web server that I want to force to use Spring's GraphQL library over GraphQL's own Java library so that I can manage the access to individual queries/mutations.
After following a guide online on how to do that, I have the following dependencies in my pom.xml:
...
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.3</version>
</parent>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-graphql</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
<version>2.7.3</version>
</dependency>
This is the simple controller I set up
#Controller
public class GraphQLController {
private MyObject obj;
public GraphQLController(MyObject obj) {
this.obj = obj;
}
#SchemaMapping
public Query query(){
return new Query(obj);
}
#SchemaMapping
public Mutation mutation(Path path){
return new Mutation(obj, path);
}
}
However, no matter the GraphQL request I am sending to the server, I always get the same response:
{
"timestamp": "2022-08-29T07:59:09.470+00:00",
"status": 405,
"error": "Method Not Allowed",
"path": "/graphql"
}
I'm pasting here the properties file just in case.
graphql.servlet.corsEnabled=false
graphql.servlet.mapping=/graphql
I also tried using
#RestController
#RequestMapping(value="/graphql", method = {RequestMethod.POST, RequestMethod.GET, RequestMethod.HEAD, RequestMethod.PUT}, path = "/graphql",
consumes = "application/json", produces = "application/json")
class GraphQLController {...
but with no success.
# Schema
type Query{
listProjects: [Project]
...
}
# Query I'm sending to localhost:8080/graphql
query {
listProjects {
name
}
}
I'm no expert of Spring and this has been bugging me for the past couple of days, can anyone help?
Many thanks!
As pointed out by #NilsHartmann in the comments, the problem was that I had the schema file under src/main/resources/ instead of src/main/resources/graphql.

FF4J - Spring Boot - Custom Authorization Manager

I am trying to create a standalone feature flag server (centrally managed feature flag micro-service) backed by spring boot starters provided by FF4J. I was able to get it up and running with the web-console and REST API as well. I am now trying to just add the support of custom authorization manager as provided in the wiki, but based on the sample provided there, I am unclear as to how the authorization manager would be aware of the user context when it gets accessed from a different microservice which is implementing the feature. Below I have provided all the relevant code snippets. If you notice in CustomAuthorizationManager class, I have a currentUserThreadLocal variable, not sure how or who is going to set that at run time for FF4J to verify the user's role. Any help on this is really appreciated, as I having issues understanding how this works.
Also note, there is a toJson method in authorization manager that needs to be overridden, not sure what needs to go over there, any help with that is also appreciated.
Custom Authorization Manager
public class CustomAuthorizationManager implements AuthorizationsManager {
private static final Logger LOG = LoggerFactory.getLogger(FeatureFlagServerFeignTimeoutProperties.class);
private ThreadLocal<String> currentUserThreadLocal = new ThreadLocal<String>();
private List<UserRoleBean> userRoles;
#Autowired
private SecurityServiceFeignClient securityServiceFeignClient;
#PostConstruct
public void init() {
try {
userRoles = securityServiceFeignClient.fetchAllUserRoles();
} catch (Exception ex) {
LOG.error("Error while loading user roles", ex);
userRoles = new ArrayList<>();
}
}
#Override
public String getCurrentUserName() {
return currentUserThreadLocal.get();
}
#Override
public Set<String> getCurrentUserPermissions() {
String currentUser = getCurrentUserName();
Set<String> roles = new HashSet<>();
if (userRoles.size() != 0) {
roles = userRoles.stream().filter(userRole -> userRole.getUserLogin().equals(currentUser))
.map(userRole -> userRole.getRoleName()).collect(Collectors.toSet());
} else {
LOG.warn(
"No user roles available, check startup logs to check possible errors during loading of user roles, returning empty");
}
return roles;
}
#Override
public Set<String> listAllPermissions() {
Set<String> roles = new HashSet<>();
if (userRoles.size() != 0) {
roles = userRoles.stream().map(userRole -> userRole.getRoleName()).collect(Collectors.toSet());
} else {
LOG.warn(
"No user roles available, check startup logs to check possible errors during loading of user roles, returning empty");
}
return roles;
}
#Override
public String toJson() {
return null;
}
}
FF4J config
#Configuration
#ConditionalOnClass({ ConsoleServlet.class, FF4jDispatcherServlet.class })
public class Ff4jConfig extends SpringBootServletInitializer {
#Autowired
private DataSource dataSource;
#Bean
public ServletRegistrationBean<FF4jDispatcherServlet> ff4jDispatcherServletRegistrationBean(
FF4jDispatcherServlet ff4jDispatcherServlet) {
ServletRegistrationBean<FF4jDispatcherServlet> bean = new ServletRegistrationBean<FF4jDispatcherServlet>(
ff4jDispatcherServlet, "/feature-web-console/*");
bean.setName("ff4j-console");
bean.setLoadOnStartup(1);
return bean;
}
#Bean
#ConditionalOnMissingBean
public FF4jDispatcherServlet getFF4jDispatcherServlet() {
FF4jDispatcherServlet ff4jConsoleServlet = new FF4jDispatcherServlet();
ff4jConsoleServlet.setFf4j(getFF4j());
return ff4jConsoleServlet;
}
#Bean
public FF4j getFF4j() {
FF4j ff4j = new FF4j();
ff4j.setFeatureStore(new FeatureStoreSpringJdbc(dataSource));
ff4j.setPropertiesStore(new PropertyStoreSpringJdbc(dataSource));
ff4j.setEventRepository(new EventRepositorySpringJdbc(dataSource));
// Set authorization
CustomAuthorizationManager custAuthorizationManager = new CustomAuthorizationManager();
ff4j.setAuthorizationsManager(custAuthorizationManager);
// Enable audit mode
ff4j.audit(true);
return ff4j;
}
}
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>feature-flag-server</artifactId>
<version>1.0.0-SNAPSHOT</version>
<name>feature-flag-server</name>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.1.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
<spring-cloud.version>Hoxton.RC2</spring-cloud.version>
<ff4j.version>1.8.2</ff4j.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
<exclusions>
<!-- resolve swagger dependency issue - start -->
<exclusion>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
</exclusion>
<!-- resolve swagger dependency issue - end -->
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- FF4J dependencies - start -->
<dependency>
<groupId>org.ff4j</groupId>
<artifactId>ff4j-spring-boot-starter</artifactId>
<version>${ff4j.version}</version>
</dependency>
<dependency>
<groupId>org.ff4j</groupId>
<artifactId>ff4j-store-springjdbc</artifactId>
<version>${ff4j.version}</version>
</dependency>
<dependency>
<groupId>org.ff4j</groupId>
<artifactId>ff4j-web</artifactId>
<version>${ff4j.version}</version>
</dependency>
<!-- FF4J dependencies - end -->
</dependencies>
<build>
<finalName>${project.artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
Full disclosure I am the maintainer of the framework.
The documentation is not good on this part, improvements are in progress. But here is some explanation for a working project.
When using AuthorizationManager:
AuthorizationManager principle should be used only if you already enabled authentication in your application (LOGIN FORM, ROLES...). If not you can think about FlipStrategy to create your own predicates.
FF4j will rely on existing security frameworks to retrieve context of logged user, this is called the principal. As such this is unlikely for you to create your own custom implementation of AuthorizationManager except you are building your own authentication mechanism.
What to do:
You will use well known framework such as Spring Security of Apache Shiro to secure your applications and simply tell ff4j to rely on it.
How to do:
Here is working example using SPRING SECURITY:
https://github.com/ff4j/ff4j-samples/tree/master/spring-boot-2x/ff4j-sample-security-spring
Here is working example using APACHE SHIRO:
https://github.com/ff4j/ff4j-samples/tree/master/spring-boot-2x/ff4j-sample-security-shiro

Unable to connect to Command Metric Stream in Spring Cloud + Hystrix + Turbine - MIME type ("text/plain") that is not "text/event-stream"

I already went through the link: Unable to connect to Command Metric Stream for Hystrix Dashboard with Spring Cloud and tried few options, but nothing worked out for yet.
I am developing Spring Cloud Code + Hystrix + Turbine.
Could you please let me know what is the issue ? I am using Spring Boot v2.0.4.RELEASE.
HystrixDashboardApplication.java
#EnableTurbine
#EnableHystrixDashboard
#SpringBootApplication
public class HystrixDashboardApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixDashboardApplication.class, args);
}
}
pom.xml
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Finchley.SR1</spring-cloud.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-hystrix-dashboard</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-turbine</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
tollrate-billboard application has the following code
TollrateBillboardApplication.java
#EnableCircuitBreaker
#SpringBootApplication
#EnableEurekaClient
public class TollrateBillboardApplication {
public static void main(String[] args) {
SpringApplication.run(TollrateBillboardApplication.class, args);
}
}
DashboardController.java
#Controller
public class DashboardController {
#LoadBalanced
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder) {
return builder.build();
}
#Autowired
private RestTemplate restTemplate;
#HystrixCommand(fallbackMethod = "getTollRateBackup")
#RequestMapping("/dashboard")
public String GetTollRate(#RequestParam int stationId, Model m) {
TollRate tr = restTemplate.getForObject("http://pluralsight-toll-service/tollrate/" + stationId, TollRate.class);
System.out.println("stationId: " + stationId);
m.addAttribute("rate", tr.getCurrentRate());
return "dashboard";
}
public String getTollRateBackup(#RequestParam int stationId, Model m) {
System.out.println("Fallback operation called");
m.addAttribute("rate", "1.00");
return "dashboard";
}
}
bootstrap.properties
spring.application.name=pluralsight-tollrate-billboard
application.properties
server.port=8082
# eureka.client.serviceUrl.defaultZone=http://localhost:8761/eureka/
eureka.client.register-with-eureka=true
eureka.client.fetch-registry=true
#http://cloud.spring.io/spring-cloud-static/Finchley.RELEASE/single/spring-cloud.html#_environment_changes
management.endpoints.web.exposure.include=hystrix.stream
CURL Command Result:
curl "http://localhost:8085/clusters"
output
[
{
"name": "PLURALSIGHT-FASTPASS-CONSOLE",
"link": "http://localhost:8085/turbine.stream?cluster=PLURALSIGHT-FASTPASS-CONSOLE"
},
{
"name": "PLURALSIGHT-TOLLRATE-BILLBOARD",
"link": "http://localhost:8085/turbine.stream?cluster=PLURALSIGHT-TOLLRATE-BILLBOARD"
}
]
EDIT-1::, I am using "hystrix-turbine"
#EnableTurbineStream
#SpringBootApplication
public class HystrixTurbineApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixTurbineApplication.class, args);
}
}
Now, I'm getting below error:
2018-09-03 22:23:45.808 WARN 2820 --- [nio-8085-exec-5] ashboardConfiguration$ProxyStreamServlet : Failed opening connection to http://localhost:8085/turbine.stream?cluster=PLURALSIGHT-FASTPASS-CONSOLE : 404 : HTTP/1.1 404
2018-09-03 22:23:45.808 WARN 2820 --- [nio-8085-exec-2] ashboardConfiguration$ProxyStreamServlet : Failed opening connection to http://localhost:8085/turbine.stream?cluster=PLURALSIGHT-FASTPASS-CONSOLE : 404 : HTTP/1.1 404
#Sayali I tried recreating the error in my own system and I managed to get it working, here are a few checks you can make:
1) The URL in your 1st screenshot is incorrect. Your stream URL in the Hystrix Dashboard should be:
http://localhost:8085/turbine.stream?cluster=PLURALSIGHT-TOLLRATE-BILLBOARD
The url should be pointing to the port of the dashboard application that has #EnableTurbine annotation in your main class.
2) Check if you are getting a response for:
http://localhost:8082/actuator/hystrix.stream
(use your browser for this)
(this should be coming from the application you have enabled hystrix on using #EnableCircuitBreaker)
If you're getting pings, then atleast your hystrix stream is accessible.
If not,
Check if you have: org.springframework.boot:spring-boot-starter-actuator in dependencies and
make sure you have the below property set in application.properties file of the application that has #EnableCircuitBreaker in the main class:
management.endpoints.web.exposure.include= hystrix.stream, info, health
Check the URL again.
3) Please get the turbine section working before moving to turbine streams, so as of now, you can make the following change:
#EnableTurbine // instead of #EnableTurbineStream
#SpringBootApplication
public class HystrixTurbineApplication {
public static void main(String[] args) {
SpringApplication.run(HystrixTurbineApplication.class, args);
}
}
Also, to use TurbineStream:
you might want to have your Hystrix commands push metrics to Turbine.
Spring Cloud enables that with messaging. To do so on the client, add
a dependency to spring-cloud-netflix-hystrix-stream and the
spring-cloud-starter-stream-* of your choice.
refer: http://cloud.spring.io/spring-cloud-static/Finchley.SR1/single/spring-cloud.html#_turbine_stream
I hope this helps. Please comment to help me know if this worked for you.
sorry late reply.
Add following configuration to instance that serve hystrix.stream
hystrix.dashboard.proxyStreamAllowList: '**'

Missing dependencies for HttpServletRequest with Jersey

I have a Standalone Jersey server running at the beginning of my JunitTest. I'm testing if my JaxRS controller works, as well as my custom HttpClient. Please note that I've always been able to use this JaxRsResourceController embedded in glassfish.
Here is the JaxRsController (light version)
#Path("root")
public class JaxRsResourceController implements
ResourceController<HttpServletRequest> {
#Context
private UriInfo context;
#Context
HttpServletRequest request;
#Context
HttpServletResponse response;
#GET
public String hello(){
System.out.println("Uri is "+this.context.getBaseUri().toString());
return "Hello "+peoples;
}
}
I have no problem with the client, but when I start the server, I have :
GRAVE: The following errors and warnings have been detected with resource and/or provider classes:
SEVERE: Missing dependency for field: javax.servlet.http.HttpServletRequest com.robustaweb.library.rest.controller.implementation.JaxRsResourceController.request
SEVERE: Missing dependency for field: javax.servlet.http.HttpServletResponse com.robustaweb.library.rest.controller.implementation.JaxRsResourceController.response
at com.sun.jersey.api.container.httpserver.HttpServerFactory.create(HttpServerFactory.java:172)
at com.robustaweb.library.rest.server.JerseyServer.startServer(JerseyServer.java:44)
Basically it says that at the #Context injection time, there is no dependency on the HttpServletRequest.
However if I remove the #Context annotations on request and response, but keep it for UriInfo context, it's ok, and I can read the Uri.
I changed a few times the Maven pom wich is now to force the libs in:
<dependencies>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-server</artifactId>
<version>1.14</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
</dependency>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.1.1</version>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
</dependency>
</dependencies>
Any idea ?
servlet dependencies were separated to another module, try adding
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-servlet</artifactId>
<version>1.14</version>
</dependency>
to your pom.
It was not easy, but I found out. The thing is that in my JUnit test, I was creating the server like this :
HttpServer server = HttpServerFactory.create(url);
But that way, you create a lightweight container that does not have servlet containers, and so is the failure reason. So in order to have it all, I used the jersey-test-framework that allow to use the Grizzly web container (or even Embedded glassfish).
Here is the maven :
<dependencies>
<dependency>
<groupId>javax.ws.rs</groupId>
<artifactId>jsr311-api</artifactId>
<version>1.1.1</version>
</dependency>
<!-- Unit test are using jersey server directly -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.8.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.sun.jersey.test.framework</groupId>
<artifactId>jersey-test-framework</artifactId>
<version>1.0.3</version>
<scope>test</scope>
</dependency>
</dependencies>
Here is the JerseyServerTest : note that it extends JerseyTest
public class JerseyServerTest extends JerseyTest {
protected String baseUri = "http://localhost:" + TestConstants.JERSEY_HTTP_PORT + "/";
public JerseyServerTest() throws Exception {
super("com.robustaweb.library.rest.controller");
/*
It's possible to NOT call the super() but to manually do :
1) ApplicationDescriptor appDescriptor = new ApplicationDescriptor()
.setRootResourcePackageName(resourcePackageName) // resource packages
.setContextPath(contextPath) //context of app
.setServletPath(servletPath); // context of spi servlet
2)setupTestEnvironment(appDescriptor);
*/
}
#Test
public void testHelloWorldRequest() {
SunRestClient client = new SunRestClient(baseUri + "root");
String result = client.GET("", null);
System.out.println(result);
}
#Test
public void testDeleteRequest() {
SunRestClient client = new SunRestClient(baseUri + "root");
String result = client.DELETE("john", null);
System.out.println(result);
}
}
And finally the Resource file, that contains #GET and #DELETE
#Path("root")
public class JaxRsController extends JaxRsResourceController{
List<String> peoples = new ArrayList<String>();
#GET
public String hello(){
System.out.println("Uri is "+getUri());
return "Hello "+peoples;
}
#DELETE
#Path("{name}")
public String deletePeople(#PathParam("name") String name){
System.out.println("deleting "+name);
this.peoples.remove(name);
return String.valueOf(peoples.size());
}
}
And now it works !
I had some help in this article, and there is a small chapter on the documentation. Beeing able to attach the source code of the Jersey framework really helped, so thantks to IntelliJ also.

Resources