Why is my OpenTelemetry java extension not working - java

I have created an extension to add some comment in the sql query. For this I have created a MySQLAddCommentInstrumentation.java class to make changes to query in Statement.java class.
Also there is one MySQLAddCommentInstrumentationModule.java class which adds this instrumentation (MySQLAddCommentInstrumentation) to its list.
When I build this extension and run my app, I do not see the comment in the query under db.statement zipkin log.
Instrumentation Class - MySQLAddCommentInstrumentation.java
Path - examples/extension/src/main/java/com/example/javaagent/mysqlinstrumentation/MySQLAddCommentInstrumentation.java
package com.example.javaagent.mysqlinstrumentation;
import static io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers.implementsInterface;
import static net.bytebuddy.matcher.ElementMatchers.named;
import static net.bytebuddy.matcher.ElementMatchers.namedOneOf;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
public class MySQLAddCommentInstrumentation implements TypeInstrumentation {
#Override
public ElementMatcher<TypeDescription> typeMatcher() {
System.out.println("Inside MYSQL- 1.1");
return implementsInterface(named("java.sql.Statement"));
}
public void transform(TypeTransformer transformer) {
System.out.println("Inside MYSQL- 1.2");
transformer.applyAdviceToMethod(
namedOneOf("executeQuery")
.and(ElementMatchers.takesArgument(0, String.class))
.and(ElementMatchers.isPublic()),
MySQLAddCommentInstrumentation.class.getName() + "$StatementAddCommentAdvice");
System.out.println("Inside MYSQL- 1.3");
}
#SuppressWarnings("unused")
public static class StatementAddCommentAdvice {
#Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(#Advice.Argument(value = 0, readOnly = false) String sql) {
System.out.println("Inside MYSQL- 1.4");
sql = sql + " -- some comment";
System.out.println("Inside MYSQL- 1.5");
}
}
InstrumentationModule - MySQLAddCommentInstrumentationModule.java
Path - examples/extension/src/main/java/com/example/javaagent/mysqlinstrumentation/MySQLAddCommentInstrumentationModule.java
package com.example.javaagent.mysqlinstrumentation;
import static java.util.Collections.singletonList;
import com.google.auto.service.AutoService;
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
import io.opentelemetry.javaagent.extension.matcher.AgentElementMatchers;
import java.util.List;
import net.bytebuddy.matcher.ElementMatcher;
#AutoService(InstrumentationModule.class)
public final class MySQLAddCommentInstrumentationModule extends InstrumentationModule {
public MySQLAddCommentInstrumentationModule() {
super("jdbc");
}
#Override
public int order() {
return 1;
}
#Override
public ElementMatcher.Junction<ClassLoader> classLoaderMatcher() {
return AgentElementMatchers.hasClassesNamed("java.sql.Statement");
}
#Override
public List<TypeInstrumentation> typeInstrumentations() {
return singletonList(new MySQLAddCommentInstrumentation());
}
}
Code where this instrumentations is expected to run:
try {
Connection con = DriverManager.getConnection(url, uname, password);
Statement statement = con.createStatement();
System.out.println(query);
ResultSet result = statement.executeQuery(query);
System.out.println(query);
} catch (SQLException e) {
e.printStackTrace();
}
I have run ./gradlew build inside examples/extension and used that extension jar to run my application
java -javaagent:/Users/vaibhavpaharia/Downloads/opentelemetry-javaagent.jar \
-Dotel.javaagent.extensions=/Users/vaibhavpaharia/ok/opentelemetry-java-instrumentation/examples/extension/build/libs/opentelemetry-java-instrumentation-extension-demo-1.0-all.jar \
-Dotel.service.name=simpleapplication \
-Dotel.traces.exporter=zipkin \
-Dserver.port=8080 \
-jar /Users/vaibhavpaharia/Downloads/simpleproject/target/simpleproject-0.0.1-SNAPSHOT.jar
After running the application with agent and extension I see that other extensions are working fine which are present in the example folder.
Also, I tried changing the order method in MySQLAddCommentInstrumentationModule.java and returned 0 from there, in that case Inside MYSQL- 1.1, Inside MYSQL- 1.2 and Inside MYSQL- 1.3 got printed so it seems the new extension is recognised while running the application. It never prints Inside MYSQL- 1.4 and Inside MYSQL- 1.5. Also, the traces stop coming to zipkin.
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended
[otel.javaagent 2023-01-05 14:50:31:010 +0530] [main] INFO io.opentelemetry.javaagent.tooling.VersionLogger - opentelemetry-javaagent - version: 1.20.2
Inside MYSQL- 1.1
Inside MYSQL- 1.2
Inside MYSQL- 1.3
[otel.javaagent 2023-01-05 14:50:34:068 +0530] [main] INFO io.opentelemetry.javaagent.shaded.instrumentation.jmx.engine.JmxMetricInsight - Empty JMX configuration, no metrics will be collected for InstrumentationScope io.opentelemetry.jmx
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v3.0.1)
2023-01-05T14:50:35.687+05:30 INFO 85143 --- [ main] c.e.s.SimpleprojectApplication : Starting SimpleprojectApplication v0.0.1-SNAPSHOT using Java 18.0.2 with PID 85143 (/Users/vaibhavpaharia/Downloads/simpleproject/target/simpleproject-0.0.1-SNAPSHOT.jar started by vaibhavpaharia in /Users/vaibhavpaharia)
2023-01-05T14:50:35.700+05:30 INFO 85143 --- [ main] c.e.s.SimpleprojectApplication : No active profile set, falling back to 1 default profile: "default"
2023-01-05T14:50:37.451+05:30 INFO 85143 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2023-01-05T14:50:37.531+05:30 INFO 85143 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2023-01-05T14:50:37.531+05:30 INFO 85143 --- [ main] o.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/10.1.4]
2023-01-05T14:50:37.643+05:30 INFO 85143 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2023-01-05T14:50:37.645+05:30 INFO 85143 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1859 ms
2023-01-05T14:50:38.254+05:30 INFO 85143 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2023-01-05T14:50:38.266+05:30 INFO 85143 --- [ main] c.e.s.SimpleprojectApplication : Started SimpleprojectApplication in 3.409 seconds (process running for 7.566)

You'll need to set the readOnly = false option on the #Argument annotation , otherwise the advice code will not overwrite the original value:
#Advice.OnMethodEnter(suppress = Throwable.class)
public static void onEnter(#Advice.Argument(value = 0, readOnly = false) String sql) {
System.out.println("Inside MYSQL-1.4");
sql = sql + " -- some comment";
System.out.println("Inside MYSQL- 1.5");
}

Related

How to define and read array of objects from spring's application.properties?

In my Java class, I want to read a variable that will give me a list of tokens in one shot and my token is an object with fields as name, value, and enabled.
#Value("authorised_applications")
private List<Token> tokenList;
How do I define this in my application.properties file so that I can read all tokens at once.
For an instance, I have tokens like:
token1
- value: 123456,
- name: specialToken,
- enabled: true
token2
- value: 56173,
- name: newToken,
- enabled: false
I have tried other links but could not find a way to read this all at once.
Want to create bean like this
#ConfigurationProperties("authorised")
#Configuration
public class AppTokenConfiguration {
private final List<TokenStore.Token> tokenList = new ArrayList<>();
#Bean
public TokenStore getTokenStore() {
return new TokenStore(tokenList.stream().collect(Collectors.toMap(TokenStore.Token::getToken, Function.identity())));
}
}
Use #ConfigurationProperties with prefix on the Class which has properties to be configured from application.properties.
application.properties:
my.tokenList[0].name=test1
my.tokenList[0].value=test2
my.tokenList[0].enabled=true
my.tokenList[1].name=test3
my.tokenList[1].value=test4
my.tokenList[1].enabled=false
server.port=8080
Student.java
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
#ConfigurationProperties("my")
#Component
public class Student {
private final List<Token> tokenList = new ArrayList<>();
public List<Token> getTokenList() {
return tokenList;
}
#Override
public String toString() {
return "TestNow [tokenList=" + tokenList + "]";
}
}
Token.java
public class Token {
private String value;
private String name;
private boolean enabled;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public boolean isEnabled() {
return enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
#Override
public String toString() {
return "Token [value=" + value + ", name=" + name + ", enabled=" + enabled + "]";
}
}
ValidateStudent.java
import javax.annotation.PostConstruct;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
#Component
public class ValidateStudent {
#Autowired
private Student student;
#PostConstruct
private void init() {
System.out.println("printing Student Object---> " + student);
}
}
Proof(output):
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.6.0-SNAPSHOT)
2021-10-20 21:17:30.083 INFO 14632 --- [ main] c.e.S.SpringBootCollectionsApplication : Starting SpringBootCollectionsApplication using Java 14.0.2 on Machine with PID 14632 (D:\workspaces\Oct20_app_properties\SpringBootCollections\target\classes started by D1 in D:\workspaces\Oct20_app_properties\SpringBootCollections)
2021-10-20 21:17:30.088 INFO 14632 --- [ main] c.e.S.SpringBootCollectionsApplication : No active profile set, falling back to default profiles: default
2021-10-20 21:17:31.869 INFO 14632 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat initialized with port(s): 8080 (http)
2021-10-20 21:17:31.891 INFO 14632 --- [ main] o.apache.catalina.core.StandardService : Starting service [Tomcat]
2021-10-20 21:17:31.891 INFO 14632 --- [ main] org.apache.catalina.core.StandardEngine : Starting Servlet engine: [Apache Tomcat/9.0.53]
2021-10-20 21:17:32.046 INFO 14632 --- [ main] o.a.c.c.C.[Tomcat].[localhost].[/] : Initializing Spring embedded WebApplicationContext
2021-10-20 21:17:32.046 INFO 14632 --- [ main] w.s.c.ServletWebServerApplicationContext : Root WebApplicationContext: initialization completed in 1869 ms
printing Student Object---> TestNow [tokenList=[Token [value=test2, name=test1, enabled=true], Token [value=test4, name=test3, enabled=false]]]
2021-10-20 21:17:32.654 INFO 14632 --- [ main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port(s): 8080 (http) with context path ''
2021-10-20 21:17:32.675 INFO 14632 --- [ main] c.e.S.SpringBootCollectionsApplication : Started SpringBootCollectionsApplication in 3.345 seconds (JVM running for 3.995)
Edit Answer:
BeanConfig Class:
#Configuration
public class AppConfig {
#Autowired
private AppTokenConfiguration appTokenConfiguration;
#Bean
public TokenStore getTokenStore() {
return new TokenStore(appTokenConfiguration.getTokenList().stream().collect(Collectors.toMap(TokenStore.Token::getToken, Function.identity())));
}
}
PropertyConfigClass:
#ConfigurationProperties("authorised")
#Component
public class AppTokenConfiguration {
private final List<TokenStore.Token> tokenList = new ArrayList<>();
public void getTokenList(){
return tokenList;
}
}

IllegalStateException: No function defined for Spring Cloud Function

I have defined a function in my Spring Cloud Function project, but after executing function (ScanQrCode) as AS Lambda I get:
2020-07-13 10:19:04.592 INFO 1 --- [ main] lambdainternal.LambdaRTEntry : Started LambdaRTEntry in 26.357 seconds (JVM running for 27.777)
2020-07-13 10:19:04.653 INFO 1 --- [ main] o.s.c.f.c.c.SimpleFunctionRegistry : Looking up function 'function' with acceptedOutputTypes: []
2020-07-13 10:19:04.731 INFO 1 --- [ main] o.s.c.f.c.c.SimpleFunctionRegistry : Looking up function 'consumer' with acceptedOutputTypes: []
2020-07-13 10:19:04.734 INFO 1 --- [ main] o.s.c.f.c.c.SimpleFunctionRegistry : Looking up function 'supplier' with acceptedOutputTypes: []
2020-07-13 10:19:04.754 INFO 1 --- [ main] o.s.c.f.c.c.SimpleFunctionRegistry : Looking up function '' with acceptedOutputTypes: []
2020-07-13 10:19:04.811 INFO 1 --- [ main] o.s.c.f.c.c.SimpleFunctionRegistry : Looking up function '' with acceptedOutputTypes: []
2020-07-13 10:19:04.811 INFO 1 --- [ main] o.s.c.f.c.c.SimpleFunctionRegistry : Looking up function '' with acceptedOutputTypes: []
No function defined: java.lang.IllegalStateException
java.lang.IllegalStateException: No function defined
at org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer.apply(AbstractSpringFunctionAdapterInitializer.java:187)
at org.springframework.cloud.function.adapter.aws.SpringBootRequestHandler.handleRequest(SpringBootRequestHandler.java:51)
The weired thing here is that I have commented out the consume, supplier and function functions...I don´t know why Spring or AWS Lambda considered them...
Here are my classes:
Spring Boot App
#SpringBootApplication
public class FoodhatQrApplication {
public static void main(String[] args) {
SpringApplication.run(FoodhatQrApplication.class, args);
}
// #Bean
// public Function<String, String> function(){
// return input -> input;
// }
//
// #Bean
// public Consumer<String> consume(){
// return input -> System.out.println("Input: " + input);
// }
//
// #Bean
// public Supplier<String> supply(){
// return () -> "Hello World";
// }
}
Function class
#Component
public class ScanQrCode implements Function<APIGatewayProxyRequestEvent, APIGatewayProxyResponseEvent>{
#Autowired
private QrCodeRepository qrCodeRepository;
#Override
public APIGatewayProxyResponseEvent apply(APIGatewayProxyRequestEvent request) {
APIGatewayProxyResponseEvent response = new APIGatewayProxyResponseEvent();
response.setStatusCode(302);
response.setHeaders(Collections.singletonMap("location" , "http://www.google.de"));
return response;
}
}
What am I missing?
Function Component Scan Adding spring.cloud.function.scan.packages might help.
Reference: https://cloud.spring.io/spring-cloud-function/reference/html/spring-cloud-function.html#_function_component_scan
You definitely need to add your function name in our case it "ScanQrCode" to AWS lambda env variables.
FUNCTION_NAME: ScanQrCode

Spring Webflux: How to use different thread for request and response

I'm using Spring Webflux and as I understand it, by using this, the thread used for receiving request and the one used for response should be different. However, whether I use netty or undertow, I end up using the same thread.
My app is a simple crud app with MySQL DB. I'm not using r2dbc but a jdbc coupled with Executor and Scheduler.
As shown in the log below, request is handled by thread [ XNIO-1 I/O-6] and the response is given by the same one.
By this, I'm assuming the thread is blocked until db operation has finished. How can I fix this?
Here's the log
2019-07-23 17:49:10.051 INFO 132 --- [ main] org.xnio : XNIO version 3.3.8.Final
2019-07-23 17:49:10.059 INFO 132 --- [ main] org.xnio.nio : XNIO NIO Implementation Version 3.3.8.Final
2019-07-23 17:49:10.114 INFO 132 --- [ main] o.s.b.w.e.undertow.UndertowWebServer : Undertow started on port(s) 8080 (http)
2019-07-23 17:49:10.116 INFO 132 --- [ main] c.n.webflux.demo.WebfluxFunctionalApp : Started WebfluxFunctionalApp in 1.262 seconds (JVM running for 2.668)
2019-07-23 17:49:10.302 DEBUG 132 --- [ XNIO-1 I/O-6] o.s.w.s.adapter.HttpWebHandlerAdapter : [4c85975] HTTP GET "/api/findall"
2019-07-23 17:49:10.322 DEBUG 132 --- [ XNIO-1 I/O-6] s.w.r.r.m.a.RequestMappingHandlerMapping : [4c85975] Mapped to public reactor.core.publisher.Mono<java.util.List<com.webflux.demo.model.TypeStatus>> com.webflux.demo.controller.MonitoringController.findAll()
2019-07-23 17:49:10.337 DEBUG 132 --- [ XNIO-1 I/O-6] o.s.w.r.r.m.a.ResponseBodyResultHandler : Using 'application/json;charset=UTF-8' given [*/*] and supported [application/json;charset=UTF-8, application/*+json;charset=UTF-8, text/event-stream]
2019-07-23 17:49:10.338 DEBUG 132 --- [ XNIO-1 I/O-6] o.s.w.r.r.m.a.ResponseBodyResultHandler : [4c85975] 0..1 [java.util.List<com.webflux.demo.model.TypeStatus>]
2019-07-23 17:49:10.347 INFO 132 --- [pool-1-thread-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Starting...
2019-07-23 17:49:10.785 INFO 132 --- [pool-1-thread-1] com.zaxxer.hikari.HikariDataSource : HikariPool-1 - Start completed.
2019-07-23 17:49:10.838 DEBUG 132 --- [pool-1-thread-1] org.springframework.web.HttpLogging : [4c85975] Encoding [[com.webflux.demo.model.TypeStatus#7b4509cb, com.webflux.demo.model.TypeStatus#22676ebe, (truncated)...]
2019-07-23 17:49:10.949 DEBUG 132 --- [ XNIO-1 I/O-6] o.s.w.s.adapter.HttpWebHandlerAdapter : [4c85975] Completed 200 OK
Also my dao is
#Repository
public class TypeStatusJdbcTemplate {
private JdbcTemplate jdbcTemplate;
public TypeStatusJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
private final static String SQL_FIND_ALL = "select * from `monitoring`.`type_status` limit 3";
public List<TypeStatus> findAll() {
return jdbcTemplate.query(SQL_FIND_ALL,
new TypeStatusMapper());
}
}
service is
#Service
public class MonitoringService {
private final Scheduler scheduler;
private TypeStatusJdbcTemplate repository;
public MonitoringService(Scheduler scheduler, TypeStatusJdbcTemplate repository) {
this.scheduler = scheduler;
this.repository = repository;
}
public Mono<List<TypeStatus>> findAll() {
return Mono.fromCallable(repository::findAll).subscribeOn(scheduler);
}
}
controller is
#RestController
#RequestMapping("/api")
public class MonitoringController {
private final MonitoringService monitoringService;
private static final Logger logger = LoggerFactory.getLogger(MonitoringController.class);
public MonitoringController(MonitoringService monitoringService) {
this.monitoringService = monitoringService;
}
#GetMapping(value="/findall")
public Mono<List<TypeStatus>> findAll() {
return monitoringService.findAll();
}
}
main file (showing scheduler)
#SpringBootApplication
public class WebfluxFunctionalApp {
public static void main(String[] args){
SpringApplication.run(WebfluxFunctionalApp.class, args);
}
#PostConstruct
public void init(){
// Setting Spring Boot SetTimeZone
TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
}
#Bean
public Scheduler jdbcScheduler() {
return Schedulers.fromExecutor(Executors.newFixedThreadPool(30));
}
}
Thread execution does not always have to be different threads. Taken from the Reactive documentation:
Reactive Schedulers
Obtaining a Flux or a Mono doesn’t necessarily mean it will run in a dedicated Thread. Instead, most operators continue working in the Thread on which the previous operator executed. Unless specified, the topmost operator (the source) itself runs on the Thread in which the subscribe() call was made.
So there is nothing that says that it has to be a new thread.

How to get command Line arguments in spring boot?

#SpringBootApplication
public class CommandLinetoolApplication {
#Value("${person.name}")
private String name;
public static void main(String... argv) {
SpringApplication.run(CommandLinetoolApplication.class, argv);
}
}
I am using eclipse so setting run configuration as
-Dspring-boot.run.arguments=--person.name=firstName
But when run my application,I am getting exception as
"Could not resolve placeholder 'person.name' in value "${person.name}"
This code works just fine (Spring Boot 2.1.4):
#SpringBootApplication
public class DemoApplication implements ApplicationRunner
{
#Value("${person.name}")
private String name;
public static void main( String[] args )
{
SpringApplication.run( DemoApplication.class, args );
}
#Override
public void run( ApplicationArguments args ) throws Exception
{
System.out.println( "Name: " + name );
}
}
Command line:
mvn spring-boot:run -Dspring-boot.run.arguments=--person.name=Test
The output:
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v2.1.4.RELEASE)
2019-04-28 22:51:09.741 INFO 73751 --- [ main] com.example.demo.DemoApplication : Starting DemoApplication on xxx-MacBook-Pro.local with PID 73751 (/Users/strelok/code/demo-sb/target/classes started by strelok in /Users/strelok/code/demo-sb)
2019-04-28 22:51:09.745 INFO 73751 --- [ main] com.example.demo.DemoApplication : No active profile set, falling back to default profiles: default
2019-04-28 22:51:10.943 INFO 73751 --- [ main] com.example.demo.DemoApplication : Started DemoApplication in 16.746 seconds (JVM running for 23.386)
Name: Test
You need to add a configuration property person.name=firstName in your application.properties
OR
Implement interface ApplicationRunner and override its run method(Correct way to read command line argument)
Example:
#SpringBootApplication
public class Application implements ApplicationRunner {
private static final Logger logger = LoggerFactory.getLogger(Application.class);
public static void main(String... args) throws Exception {
SpringApplication.run(Application.class, args);
}
#Override
public void run(ApplicationArguments args) throws Exception {
logger.info("Application started with command-line arguments: {}", Arrays.toString(args.getSourceArgs()));
logger.info("NonOptionArgs: {}", args.getNonOptionArgs());
logger.info("OptionNames: {}", args.getOptionNames());
for (String name : args.getOptionNames()){
logger.info("arg-" + name + "=" + args.getOptionValues(name));
}
boolean containsOption = args.containsOption("person.name");
logger.info("Contains person.name: " + containsOption);
}
}
You need to change your eclipse VM arguments as -Dperson.name=dhanraj
One more thing is there is no use to add private String name; in main class.
Because main method is static method, so you need to create object to access name
variable and ultimately new object gives you null value not the value you set dhanraj.
So Use this variable in Controller or Service part.

Spring unit test MockMvc fails when using custom filter in Spring Security

I have a web application that should only be callable from specific IP addresses. Other than that, there is no need for authentication or for authorization; if you're coming from the right IP, you can see everything.
To that end, searching StackOverflow and other places, I found a number of suggestions for filtering requests by IP address in Spring Security. They all took this form (extending WebSecurityConfigurerAdapter using java configuration):
http.authorizeRequests().anyRequest().access("hasIpAddress('127.0.0.1/0')");
However, that never worked for me; it never rejected any request, no matter what IP address I made the request from. Instead, I implemented my IP filtering with a custom filter like this:
#Configuration
#EnableWebSecurity
#PropertySource("classpath:config.properties")
#EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig extends WebSecurityConfigurerAdapter {
private static final Logger logger = LoggerFactory.getLogger(SecurityConfig.class);
#Autowired
private Environment env;
#Override
protected void configure(HttpSecurity http) throws Exception {
String ipRange = env.getRequiredProperty("trusted_ips");
logger.info("##### SETTING UP SECURITY CONFIGURATION #############");
logger.info("## trusted_ips: " + ipRange);
logger.info("##### SETTING UP SECURITY CONFIGURATION - END #############");
http.addFilterBefore(new IPSecurityFilter(ipRange), J2eePreAuthenticatedProcessingFilter.class)
.authorizeRequests().antMatchers("/**").permitAll();
}
}
My IPSecurityFilter:
public class IPSecurityFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(IPSecurityFilter.class);
private String[] ipAddresses;
public IPSecurityFilter(String strIPAddresses) {
logger.info("#### Our IP Address whitelist: " + strIPAddresses);
this.ipAddresses = strIPAddresses.split(",");
}
#Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
logger.info("Checking whether request should be allowed: " + request.getRequestURI());
logger.info("### Request is coming from IP address: " + request.getRemoteAddr());
for (String ipAddress : ipAddresses) {
if (ipAddress.equals(request.getRemoteAddr())) {
logger.info("### Allowing request from ip address: " + request.getRemoteAddr());
return; // We accept requests from this IP address
}
}
// The remote IP address isn't on our white list; throw an exception
throw new AccessDeniedException("Access has been denied for your IP address: " + request.getRemoteAddr());
}
}
This seems to work in that the request is rejected if it originates from an IP Address that isn't on my white list.
However, with this configuration, my unit (using MockMvc) test fails; and it fails in a way that I would never have expected. When the unit test runs, it appears to use the Spring Security configuration correctly and the request passes the security test (the IP white list includes 127.0.0.1 and according to the log that is generated while the test is being run, the request is coming from that IP). However, the request never seems to be routed to my controller.
Here is my test:
#RunWith(SpringRunner.class)
#WebMvcTest()
//#WebMvcTest(value = HandlerController.class)
#AutoConfigureMockMvc
#Import(SecurityConfig.class)
public class HandlerControllerTest {
#Autowired
private MockMvc mvc;
#Test
public void getIndex() throws Exception {
mvc.perform(MockMvcRequestBuilders.get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json("{\"services\":[\"OutboundMessageService\"]}", true));
}
}
And finally, here is my controller (please ignore the idiotic way that I'm generating the JSON return value, it's still very early in development):
#RestController
public class HandlerController {
private static final Logger logger = LoggerFactory.getLogger(HandlerController.class);
#RequestMapping("/")
public String index() {
logger.info("### handling a request for / ###");
return "{\"services\":[\"OutboundMessageService\"]}";
}
}
And here are the test results:
2017-11-14 08:29:12.151 INFO 25412 --- [ main] c.z.s.controllers.HandlerControllerTest : Starting HandlerControllerTest on 597NLL1 with PID 25412 (started by User in C:\Development\KnowledgeBin\NewArchitecture\OutboundMessageHandler)
2017-11-14 08:29:12.152 INFO 25412 --- [ main] c.z.s.controllers.HandlerControllerTest : No active profile set, falling back to default profiles: default
2017-11-14 08:29:12.178 INFO 25412 --- [ main] o.s.w.c.s.GenericWebApplicationContext : Refreshing org.springframework.web.context.support.GenericWebApplicationContext#209da20d: startup date [Tue Nov 14 08:29:12 MST 2017]; root of context hierarchy
2017-11-14 08:29:13.883 INFO 25412 --- [ main] b.a.s.AuthenticationManagerConfiguration :
Using default security password: 56e3fab8-f7fb-4fbd-b2d2-e37eae8cef5e
2017-11-14 08:29:13.962 INFO 25412 --- [ main] c.z.services.security.IPSecurityFilter : #### Our IP Address whitelist: 122.22.22.22,127.0.0.1
2017-11-14 08:29:14.086 INFO 25412 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Creating filter chain: org.springframework.security.web.util.matcher.AnyRequestMatcher#1, [org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter#3f4f9acd, org.springframework.security.web.context.SecurityContextPersistenceFilter#470a9030, org.springframework.security.web.header.HeaderWriterFilter#60c16548, org.springframework.security.web.csrf.CsrfFilter#435ce306, org.springframework.security.web.authentication.logout.LogoutFilter#607b2792, com.zpaper.services.security.IPSecurityFilter#46baf579, org.springframework.security.web.savedrequest.RequestCacheAwareFilter#27494e46, org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter#36453307, org.springframework.security.web.authentication.AnonymousAuthenticationFilter#4bf324f9, org.springframework.security.web.session.SessionManagementFilter#452c8a40, org.springframework.security.web.access.ExceptionTranslationFilter#39ce27f2, org.springframework.security.web.access.intercept.FilterSecurityInterceptor#5767b2af]
2017-11-14 08:29:14.183 INFO 25412 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/]}" onto public java.lang.String com.zpaper.services.controllers.HandlerController.index()
2017-11-14 08:29:14.184 INFO 25412 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/OutboundMessageService]}" onto public java.lang.String com.zpaper.services.controllers.HandlerController.outboundMessage()
2017-11-14 08:29:14.189 INFO 25412 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2017-11-14 08:29:14.190 INFO 25412 --- [ main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],produces=[text/html]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest,javax.servlet.http.HttpServletResponse)
2017-11-14 08:29:14.243 INFO 25412 --- [ main] c.z.s.config.HandlerWebConfiguration : #### My Configuration handler was called ####
2017-11-14 08:29:14.253 INFO 25412 --- [ main] o.s.w.s.handler.SimpleUrlHandlerMapping : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler]
2017-11-14 08:29:14.313 INFO 25412 --- [ main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for #ControllerAdvice: org.springframework.web.context.support.GenericWebApplicationContext#209da20d: startup date [Tue Nov 14 08:29:12 MST 2017]; root of context hierarchy
2017-11-14 08:29:14.784 INFO 25412 --- [ main] o.s.b.t.m.w.SpringBootMockServletContext : Initializing Spring FrameworkServlet ''
2017-11-14 08:29:14.784 INFO 25412 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization started
2017-11-14 08:29:14.805 INFO 25412 --- [ main] o.s.t.web.servlet.TestDispatcherServlet : FrameworkServlet '': initialization completed in 21 ms
2017-11-14 08:29:14.897 INFO 25412 --- [ main] c.z.s.controllers.HandlerControllerTest : Started HandlerControllerTest in 3.095 seconds (JVM running for 3.995)
2017-11-14 08:29:14.981 INFO 25412 --- [ main] c.z.services.security.IPSecurityFilter : Checking whether request should be allowed: /
2017-11-14 08:29:14.981 INFO 25412 --- [ main] c.z.services.security.IPSecurityFilter : ### Request is coming from IP address: 127.0.0.1
2017-11-14 08:29:14.981 INFO 25412 --- [ main] c.z.services.security.IPSecurityFilter : ### Allowing request from ip address: 127.0.0.1
MockHttpServletRequest:
HTTP Method = GET
Request URI = /
Parameters = {}
Headers = {Accept=[application/json]}
Handler:
Type = null
Async:
Async started = false
Async result = null
Resolved Exception:
Type = null
ModelAndView:
View name = null
View = null
Model = null
FlashMap:
Attributes = null
MockHttpServletResponse:
Status = 200
Error message = null
Headers = {X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY]}
Content type = null
Body =
Forwarded URL = null
Redirected URL = null
Cookies = []
Tests run: 1, Failures: 0, Errors: 1, Skipped: 0, Time elapsed: 3.363 sec <<< FAILURE! - in com.zpaper.services.controllers.HandlerControllerTest
getIndex(com.zpaper.services.controllers.HandlerControllerTest) Time elapsed: 0.12 sec <<< ERROR!
org.json.JSONException: Unparsable JSON string:
at org.skyscreamer.jsonassert.JSONParser.parseJSON(JSONParser.java:42)
As can be seen in the log messages, the IP filter is being invoked and is allowing the request to continue. However, the debug string that is being emitted in my handler is nowhere to be seen and the return body is blank. Can anyone tell me why my security filter would prevent MockMvc from having its request successfully routed to my controller?
Final Note: if I use the http.authorizeRequests().anyRequest().access("hasIpAddress('127.0.0.1/0')"); configuration that I first listed or completely get rid of Spring Security by removing my SecurityConfig class, the request is routed successfully to my handler.
I figured out how to make the test work. I was not able to find a single article that answered my question but by taking different suggestions from multiple blog posts, I came up with this which works for me:
#RunWith(SpringRunner.class)
#WebMvcTest(controllers = HandlerController.class)
public class HandlerControllerTest {
private MockMvc mvc;
#Autowired
private WebApplicationContext webApplicationContext;
#Before
public void setUp() {
// mvc = MockMvcBuilders.standaloneSetup(new HandlerController()).build();
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();
}
#Test
public void getIndex() throws Exception {
mvc.perform(get("/").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json("{\"services\":[\"OutboundMessageService\"]}", true));
}
#Test
public void getMessageService() throws Exception {
mvc.perform(get("/OutboundMessageService").accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andExpect(content().json("{\"status\": \"SUCCESS\"}", true));
}
}
As you can see, I am no longer auto-wiring the MockMvc object and allowing it be automatically set up but am instead setting it up myself in the setUp() method. The commented-out line in the setUp() method works to successfully test my controller also but it doesn't route the request through my Spring Security IP address filter. I'm leaving it in so that users that don't need to test Spring Security can see an alternate method to set up the MockMvc object. The uncommented line sets up a MockMvc object such that it runs the request through both the security filters and my controller.
I know it is late but for others looking for the answer, you can add that filter to the MockMvc object like this:
#Autowired
private MockMvc mvc;
#Autowired
private YourCustomFilter filter;
#Before
public void setUp() {
mvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
.addFilter(filter).build();
}

Categories