AtlassianHostRestClients object is null in Thread class?

We are developing a plugin for Jira.

I have the code below in MultiThread.class:


@Service
public class MultiThread extends Thread {

@Autowired
	private AtlassianHostRestClients restClients; - getting null value 

String method(){

JsonData responseEntity = restTemplate.getForObject(
						UriComponentsBuilder.fromUriString(urlString).buildAndExpand("\"" + p + "\"",
								"\"" + issueType + "\"", "\"" + date + "\"", "\"" + date + "\"").toUri(),
						JsonData.class);

return result;
}
}

But receiving an error with restClients has null pointer exception. This came after I implemented Thread concept to parallel run multiple REST api calls

However, remaining classes have data in restClients object.

Please help AtlassianHostRestClients in multi-thread concept.

Hi @ramjeevan.tadi,

I assume you meant to post this question under Jira Cloud, not Jira Server. (atlassian-connect-spring-boot is of no use to you if you are building for Jira Server.)

There is nothing special about the Thread class that would prevent you from injecting components into it. So my guess is that you are using the new operator to create the MultiThread instance yourself, instead of letting Spring manage it, and thereby putting both @Service and @Autowired annotations out of play.

Instead, have a look at the Spring tutorial Creating Asynchronous Methods. atlassian-connect-spring-boot already uses some @Async methods internally.

1 Like

Thank you for the response!

Yes, we are developing for JIRA Cloud :stuck_out_tongue:

Actually my code is something like this,

@Service
public class MultiThread extends Thread {

private AtlassianHostRestClients restClients;

String p;
string date;
Map<String, Integer> riskAcceptableMap = new LinkedHashMap<String, Integer>();

public MultiThread(SelectChoiceEntity sce, DateChoiceE

ntity dce) {

** p = sce.getProjectname();**
** date = dce.getDate();**

**} **

@Override
public void run() {

JsonData responseEntity = restTemplate.getForObject(
UriComponentsBuilder.fromUriString(urlString).buildAndExpand(""" + p + “”",
""" + issueType + “”", “”" + date + “”", “”" + date + “”").toUri(),
JsonData.class);

riskAcceptableMap.put(urlName, responseEntity.getTotal());
}

}

Here, I’m using constructor to provide difference values to get respective results.

from controller class,

@Autowired
MultiThread t1;

@Autowired
MultiThread t2;

{ RequestMethod

t1 = new MultiThread(sce, dce);
sce.Set(0);
t2 = new MultiThread(sce, dce);

t1.join(); t2.join();

}

“Instead, have a look at the Spring tutorial Creating Asynchronous Methods. atlassian-connect-spring-boot already uses some @Async methods internally.”

Sure, Epehrson. I’ll definitely look into this information.

Is the above code properly done, please suggest?

Sure, I guess that code snippet would work as well. Although, to reiterate, I would still not recommend extending Thread as a way of achieving concurrency in a Spring application.

1 Like

Okay, Epehrson. Thank you!

I’ll will now work on Asynchronous Methods to do this job.

Hi Epehrson,

This is the error I’m receiving when tried with Spring Async method,

java.lang.IllegalStateException: Was asked to authenticate the rest client as the current acting user on the host, but none could be inferred from the current request
at com.atlassian.connect.spring.internal.request.AtlassianHostRestClientsImpl.lambda$authenticatedAsHostActor$0(AtlassianHostRestClientsImpl.java:52) ~[atlassian-connect-spring-boot-core-1.3.5.jar:na]
at java.util.Optional.orElseThrow(Optional.java:290) ~[na:1.8.0_101]
at

Hi Epehrson,

tried with the code below( it worked ),

@RequestMapping(method = RequestMethod.GET)
@Async
public CompletableFuture<Double> findCount(@AuthenticationPrincipal AtlassianHostUser hostUser,
		SelectChoiceEntity sce, int dce) throws InterruptedException {

JsonData responseEntity = restClients.authenticatedAs(hostUser)
				.getForObject(
						UriComponentsBuilder.fromUriString(urlString).buildAndExpand("\"" + p + "\"",
								"\"" + issueType + "\"", "\"" + date + "\"", "\"" + date + "\"").toUri(),
						JsonData.class);


}

from controller,

        CompletableFuture<Double> page1 = jiraLookupService.findCount(hostUser, sce, 0);

	CompletableFuture<Double> page2 = jiraLookupService.findCount(hostUser, sce, -1);

	CompletableFuture<Double> page3 = jiraLookupService.findCount(hostUser, sce, -2);

	CompletableFuture.allOf(page1, page2, page3).join();

But I’m passing hostUser in the method call, is that a correct way?

When I’m trying with restClients.authenticatedAsHostActor(), is it not working and getting the error below,

“java.lang.IllegalStateException: Was asked to authenticate the rest client as the current acting user on the host, but none could be inferred from the current request”

when debugging, there is no data in getAuthentication method in AtlassianHostRestClients class,

Please suggest, is it right passing HostUser in the calling method and using restClients.authenticatedAs(hostUser).getForObject(…)?

when debugging, there is no data in getAuthentication method in AtlassianHostRestClients class,

Please suggest, is it right passing HostUser in the calling method and using restClients.authenticatedAs(hostUser).getForObject(…)?

Sure. But I take it that your findCount() method is located in the JiraLookupService class, and that it is not a controller, so the @RequestMapping and @AuthenticationPrincipal annotations won’t work in that context.

@RequestMapping(method = RequestMethod.GET)

Removed the above annotation in JiraLookupService calss.

this is my controller method,

@RequestMapping(value = "/riskAcceptableLevelControl", method = RequestMethod.GET)
public String riskAcceptableLevelControl(@AuthenticationPrincipal AtlassianHostUser hostUser,

		@ModelAttribute("mychoice") SelectChoice mychoice, Model model)
		throws IOException, InterruptedException, ExecutionException {

        CompletableFuture<Double> page1 = jiraLookupService.findCount(hostUser, sce, 0);

	CompletableFuture<Double> page2 = jiraLookupService.findCount(hostUser, sce, -1);

	CompletableFuture<Double> page3 = jiraLookupService.findCount(hostUser, sce, -2);

	CompletableFuture.allOf(page1, page2, page3).join();

   }

Here, I’m receiving hostUser details in controller and passing that hostUser in findCount method which is in JiraLookupService class.

It is working, but is it correct way passing hostUser in every call?

Correct? I think it’s a matter of taste.

You could also access the SecurityContextHolder directly in your service class, emulating what @AuthenticationPrincipal does.

Optional<AtlassianHostUser> atlassianHostUser = Optional.ofNullable(SecurityContextHolder.getContext().getAuthentication())
                .map(Authentication::getPrincipal)
                .filter(AtlassianHostUser.class::isInstance)
                .map(AtlassianHostUser.class::cast);
1 Like

Hi Epehrson,

Thanks for your help! It worked…

However, could you help differentiating the below two authentication methods?

 1. restClients.authenticatedAs(hostUser).getForObject()  ---  ( my async requirement worked with this method ) 
 2. restClients.authenticatedAsHostActor().getForObject() --- ( Error: Was asked to authenticate the rest client as the current acting user ....)

Please help

I hope the Javadoc for AtlassianHostRestClients is clear enough.

AtlassianHostRestClients#authenticatedAsHostActor() retrieves the currently authenticated user from the Spring Security context, which seems to be empty. Baeldung’s Spring Security Context Propagation with @Async seems to explain why, and provide a solution.