I am getting below error when I try to use #Async annotation.
java.lang.IllegalStateException: No thread-bound request found: Are you referring to request attributes outside of an actual web request, or processing a request outside of the originally receiving thread? If you are actually operating within a web request and still receive this message, your code is probably running outside of DispatcherServlet/DispatcherPortlet: In this case, use RequestContextListener or RequestContextFilter to expose the current request.
Code:
#Async
#Override
#Transactional
public void triggerEmailCommunication(List<String> eventCode, Integer authorizationId, Integer claimId,
boolean callMethod) {
Map<String, Object> emailBody = new HashMap<>();
try {
if (callMethod) {
LOGGER.info("Communication async flow triggerEmailCommunication method starts.");
emailBody = emailBodyObject(authorizationId, claimId, eventCode);
}
private Map<String, Object> emailBodyObject(Integer authorizationId, Integer claimId, List<String> eventCode)
throws CareBusinessServiceException {
LOGGER.info("EmailBodyObject method starts.");
Map<String, Object> emailBody = new HashMap<String, Object>();
EmailClaimDetailsVO emailClaimDetails = new EmailClaimDetailsVO();
ClaimAuthorizationVO claimAuthVO = new ClaimAuthorizationVO();
Claim claim = new Claim();
Authorization authorization = new Authorization();
List<String> rejectReasonList = new ArrayList<>();
Provider provider = new Provider();
String providerName = null;
String claimIntimationNbr = null;
String authorizationNbr = null;
SimpleDateFormat formatter = new SimpleDateFormat(AmhiConstants.DATE_FORMAT_DD_MM_YYYY);
try {
Integer claimIntimationId = null;
if (null != claimId) {
claim = enableBusinessService.retrieveClaimDetails(claimId);
} catch(Exception e) {
}
}
DAO Layer
#Override
public Claim retrieveClaimIdRecord(Integer claimId) {
CriteriaBuilder builder = manager.getCriteriaBuilder();
CriteriaQuery<Claim> criteriaQuery = builder.createQuery(Claim.class);
Root<Claim> root = criteriaQuery.from(Claim.class);
ArrayList<Predicate> conditions = new ArrayList<>();
conditions.add(builder.equal(root.get(Claim_.claimId), claimId));
criteriaQuery.select(root).where(conditions.toArray(new Predicate[] {}));
javax.persistence.Query query = manager.createQuery(criteriaQuery);
List<Claim> claims = query.getResultList();
if(CollectionUtils.isNotEmpty(claims)){
return claims.get(0);
}
return new Claim();
}
The value is getting retrieved from DB. But I am getting above exception as mentioned.
In my case it happened when I created a bean at request level and tried to access it from another thread created using #Async annotation. The bean information was stored in the thread local of the original thread only.
Are you using something created and stored in the thread local of the base thread in the function or subsequent functions after #Async notification?
#Async creates another thread but doesn't copy the thread local of the original thread which causes the issue.
See this - https://jira.spring.io/browse/SPR-6873
Related
I would like to retrieve all devices managed by Intune (managed devices) using the Microsoft Graph Java SDK. I have created the app in Microsoft Azure and given the appropriate API permissions:
API Permissions
The following code creates a graphClient object and a method that retrieves all managed devices.
#Service
public class AzureServiceDefault implements AzureService
{
private static final String CLIENT_ID = "XXXXXXXXXXXXXXXXXXXXXXXX";
private static final List<String> SCOPES = Arrays.asList(new String[]{"https://graph.microsoft.com/.default"});
private static final String TENANT = "XXXXXXXXXXXXXXXXXXXXXXXX";
private static final String CLIENT_SECRET = "XXXXXXXXXXXXXXXXXXXXXXXX";
ClientCredentialProvider authProvider = new ClientCredentialProvider(CLIENT_ID, SCOPES, CLIENT_SECRET, TENANT, NationalCloud.Global);
IGraphServiceClient graphClient;
public AzureServiceDefault()
{
graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
}
#Override
public List<IntuneDevice> getManagedDevices()
{
IManagedDeviceCollectionRequestBuilder managedDeviceRequestBuilder;
IDeviceManagementRequestBuilder builder = graphClient.deviceManagement();
IDeviceManagementRequest managedDevicesRequest = builder.buildRequest();
List<ManagedDevice> managedDevices = new ArrayList<>();
List<IntuneDevice> allManagedDevices = new ArrayList<>();
do {
try {
DeviceManagement deviceManagement = managedDevicesRequest.get();
ManagedDeviceCollectionPage managedDevicesCollectionPage = deviceManagement.managedDevices;
//Process items in the response
managedDevices.addAll(managedDevicesCollectionPage.getCurrentPage());
managedDevices.stream().forEach((device) -> allManagedDevices.add(new IntuneDevice(device.id,
device.userId,
device.deviceName,
device.managedDeviceOwnerType.toString(),
device.operatingSystem,
device.osVersion,
device.complianceState.toString(),
device.azureADRegistered,
device.azureADDeviceId,
device.userPrincipalName,
device.model,
device.manufacturer,
device.serialNumber)));
//Build the request for the next page, if there is one
managedDeviceRequestBuilder = managedDevicesCollectionPage.getNextPage();
if (managedDeviceRequestBuilder == null)
{
managedDevicesRequest = null;
}
else
{
managedDevicesRequest = (IDeviceManagementRequest) managedDeviceRequestBuilder.buildRequest();
}
}
catch(ClientException ex)
{
ex.printStackTrace();
managedDevicesRequest = null;
}
} while (managedDevicesRequest != null);
return allManagedDevices;
}
}
The problem is that the variable managedDevices turns out to be null and this is the error message:
SEVERE: Servlet.service() for servlet [dispatcher] in context with path [/] threw exception [Request processing failed; nested exception is java.lang.NullPointerException: Cannot invoke "com.microsoft.graph.requests.extensions.ManagedDeviceCollectionPage.getCurrentPage()" because "managedDevicesCollectionPage" is null] with root cause
java.lang.NullPointerException: Cannot invoke "com.microsoft.graph.requests.extensions.ManagedDeviceCollectionPage.getCurrentPage()" because "managedDevicesCollectionPage" is null
What do I need to change to make this code work? I am succesfully able to retrieve all users in Azure AD, but I am having difficulties getting data from Intune/Endpoint Manager. Do I need to make changes to the SCOPES?
It should be possible to retrieve all managed devices as the REST API for it is https://graph.microsoft.com/v1.0/deviceManagement/managedDevices
Thanks for your help
This MS Graph API does not support application permissions, so you couldn't list managedDevices with ClientCredentialProvider. ClientCredentialProvider is based on client credential flow that requires application permission.
You could use AuthorizationCodeProvider to get the list. And follow this to get AUTHORIZATION_CODE first.
String CLIENT_ID = "xxxxxx";
List<String> SCOPES = Arrays.asList(new String[] { "https://graph.microsoft.com/.default" });
String CLIENT_SECRET = "xxxxxx";
String TENANT = "xxxxxx";
String AUTHORIZATION_CODE = "";
String REDIRECT_URL = "xxxxxx";
AuthorizationCodeProvider authProvider = new AuthorizationCodeProvider(CLIENT_ID, SCOPES, AUTHORIZATION_CODE,
REDIRECT_URL, NationalCloud.Global, TENANT, CLIENT_SECRET);
IGraphServiceClient graphClient = GraphServiceClient.builder().authenticationProvider(authProvider).buildClient();
IManagedDeviceCollectionPage managedDeviceCollectionPage = graphClient.deviceManagement().managedDevices().buildRequest().get();
List<ManagedDevice> managedDeviceList = managedDeviceCollectionPage.getCurrentPage();
I am new to spring framework.I have started using Retryable annotation in my mvc application.I have added #EnableRetry on my config class.
#Configuration
#EnableScheduling
#EnableRetry
class ApplicationConfig {
I have my MachinesContainer class in which I am calling some other REST APIs.On that method I have used #Retryable annotation with configuration provided to that.
#Retryable(value = {NullPointerException.class},maxAttempts = 3,backoff = #Backoff(2000))
public static void getMachineContainer(ResponseEntity<MachinesContainer> machinesContainer,String ipsByGeoUrl,HttpEntity<?> requestEntity) throws Exception {
if(machinesContainer==null) {
throw new NullPointerException();
}
machinesContainer = restTemplate
.exchange(ipsByGeoUrl, HttpMethod.POST,
requestEntity, MachinesContainer.class);
}
It directly calls to exception instead of calling "getMachineContainer" 3 times.
#Component
public class Query{
#Override
public MachinesContainer getIpsByGeo(String city, String state, String country) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.TEXT_PLAIN);
String query = bundle.getString("ipsByGeo.sqlQuery");
// Build Map with params to IPsByGeo query
HashMap<String, String> args = new HashMap<String, String>();
args.put("cityKey", city.trim().toUpperCase());
args.put("countryKey", country.trim());
if (state != null && state.length() >= 1) {
args.put("stateClause", " and r.state='" + state.trim() + "'");
} else {
args.put("stateClause", "");
}
//Generate query with values
StrSubstitutor sub = new StrSubstitutor(args,"$","$");
query = sub.replace(query);
HttpEntity<?> requestEntity = new HttpEntity<String>(query, headers);
String environment = System.getenv(SERVICE_ENVIRONMENT);
logger.debug("getIpsByGeo service environment is:{}",environment);
String ipsByGeoUrl = <Some API URL>;
try {
ResponseEntity<MachinesContainer> machinesContainer = null;
getMachineContainer(machinesContainer,ipsByGeoUrl,requestEntity);
return machinesContainer.getBody();
}
catch (Exception e) {
throw e;
}
}
}
Please suggest some solutions.
I am using spring version - 4.2.3.RELEASE
Java version - 1.8
spring-retry - 1.2.1.RELEASE
spring-aop - 4.2.5.RELEASE
aspectjweaver - 1.8.8
Couple of things
1. #Retryable ia based on Spring AOP which uses proxy, and hence it doesn't work with private method calls(I mean methods in the same class)
2. Same applies to static methods.
I'm trying to mock authentication in Java for an authentication. This is my test class for mocking the code:
MockitoAnnotations.initMocks(this);
mvc = MockMvcBuilders.standaloneSetup(this.controller).build();
final List<AuthenticationProvider> providers = mock(ArrayList.class);
final AbstractUserDetailsAuthenticationProvider provider = mock(
AbstractUserDetailsAuthenticationProvider.class);
when(provider.supports(any(Class.class))).thenReturn(false);
when(providers.size()).thenReturn(1);
session = new MockHttpSession();
when(providers.get(anyInt())).thenReturn(provider);
when(request.getSession()).thenReturn(session);
when(request.getSession(false)).thenReturn(session);
when(providers.iterator()).thenReturn(new Iterator<AuthenticationProvider>() {
private int currentIndex = 0;
#Override
public AuthenticationProvider next() {
return providers.get(currentIndex++);
}
#Override
public boolean hasNext() {
return currentIndex < providers.size() && providers.get(currentIndex) != null;
}
});
SingleProviderAuthenticationManager manager = new SingleProviderAuthenticationManager(providers);
Map<String, AuthenticationManager> map = new HashMap<String, AuthenticationManager>();
map.put("db", manager);
filter.setAuthenticationManagerMap(map);
when(request.getMethod()).thenReturn("POST");
when(request.getParameter("username")).thenReturn("admin");
when(request.getParameter("password")).thenReturn("admin");
List<User> users = new ArrayList<User>();
User user = new User();
user.setSourceSystem("db");
users.add(user);
when(userService.getUserReferenceByUsername("admin")).thenReturn(users);
auth = filter.attemptAuthentication(request, response);
Now, on the line where I put the manager in the map.put() method, when I put "db" it actually gives the provider manager as null and I get the NullPointerException in the ProviderManager.
for (AuthenticationProvider provider : getProviders()) {
if (!provider.supports(toTest)) {
continue;
}
Even though I've tested the same thing in my main code with the providers I pass its still showing a NullPointerException. And if I put "ldap" instead it gives me a NullPointerException in the UsernamePasswordAuthenticationFilter here:
(last line where the return happens)
password = "";
}
username = username.trim();
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
// Allow subclasses to set the "details" property
setDetails(request, authRequest);
return this.getAuthenticationManager().authenticate(authRequest);
}
I am stuck here, both these things when I pass them are not at all null. Any help?
Don't mock ArrayList instead create new array list and send it to actual class like below :
final List<AuthenticationProvider> providers = new ArrayList<>();
providers.add("AuthenticationProvider object values");
// when(providers.size()).thenReturn(1); -- no need of doing this stuff if you create direct arraylist object.
PS- If you are working with collections, do prefer creating new objects and send that object to actual class instead of mocking and sending, that is the best practice.
Hope it's useful.
I wrote the following code to test the performance of both the sync RestTemplate and AsyncRestTemplate. I just ran it a few times manually on POSTMAN.
We are just passing 10 references into a GET call so that we can return 10 links:
RestTemplate - synchronous and returns in 2806ms:
ArrayList<String> references = new ArrayList<>();
ArrayList<String> links = new ArrayList<>();
RestTemplate restTemplate = new RestTemplate();
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
for (int i = 0; i < 10; i++) {
ResponseEntity<String> resource = restTemplate.getForEntity(references.get(i), String.class);
links.add(resource.getBody().toString());
}
RestTemplate - asynchronous and returns in 2794ms:
//Creating a synchronizedList so that when the async resttemplate returns, there will be no concurrency issues
List<String> links = Collections.synchronizedList(new ArrayList<String>());
//CustomClientHttpRequestFactory just extends SimpleClientHttpRequestFactory but disables automatic redirects in SimpleClientHttpRequestFactory
CustomClientHttpRequestFactory customClientHttpRequestFactory = new CustomClientHttpRequestFactory();
//Setting the ThreadPoolTaskExecutor for the Async calls
org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor pool = new org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor();
pool.setCorePoolSize(5);
pool.setMaxPoolSize(10);
pool.setWaitForTasksToCompleteOnShutdown(true);
pool.initialize();
//Setting the TaskExecutor to the ThreadPoolTaskExecutor
customClientHttpRequestFactory.setTaskExecutor(pool);
ArrayList<String> references = new ArrayList<>();
ArrayList<String> links = new ArrayList<>();
AsyncRestTemplate asyncRestTemplate = new AsyncRestTemplate(customClientHttpRequestFactory);
restTemplate.getMessageConverters().add(new StringHttpMessageConverter());
for (int i = 0; i < 10; i++) {
Future<ResponseEntity<String>> resource = asyncRestTemplate.getForEntity(references.get(i), String.class);
ResponseEntity<String> entity = resource.get(); //this should start up 10 threads to get the links asynchronously
links.add(entity.getBody().toString());
}
In most cases, both methods actually return back the results with a very similar time, averaging 2800ms in both async and sync calls.
Am I doing something incorrect as I would have expected the async call to be much faster?
Nowadays, AsyncRestTemplate is #Deprecated in favor of WebClient. So nobody should use that class anymore!
https://docs.spring.io/spring/docs/current/javadoc-api/org/springframework/web/reactive/function/client/WebClient.html
I would say that you're missing the real benefits of the AsyncRest here.
You should add callbacks to each requests you're sending so that the response will be processes only when available.
Indeed, the getForEntity method of an AsyncRestTemplate returns a ListenableFuture to which you can connect a callback task. See the official doc ListenableFuture for further information.
For example in your case it could be:
for (int i = 0; i < 10; i++) {
ListenableFuture<ResponseEntity<String>> response = asyncRestTemplate.getForEntity(references.get(i), String.class);
response.addCallback(new ListenableFutureCallback<ResponseEntity<String>>() {
#Override
public void onSuccess(ResponseEntity<String> result) {
// Do stuff onSuccess
links.add(result.getBody().toString());
}
#Override
public void onFailure(Throwable ex) {
log.warn("Error detected while submitting a REST request. Exception was {}", ex.getMessage());
}
});
}
The tricky thing with Java Future is that it's not composable and it's really easy to block.
In this case, calling future.get() makes your code block and wait until the response is back. In fact, this approach makes sequential calls and does not leverage the async nature of this RestTemplate implementation.
The simplest way to fix this is to separate it in two loops:
ArrayList<Future<ResponseEntity<String>>> futures = new ArrayList<>();
for (String url : references.get()) {
futures.add(asyncRestTemplate.getForEntity(url, String.class)); //start up to 10 requests in parallel, depending on your pool
}
for (Future<ResponseEntity<String>> future : futures) {
ResponseEntity<String> entity = future.get(); // blocking on the first request
links.add(entity.getBody().toString());
}
Obviously there are more elegant solutions, especially if using JDK8 streams, lambdas and ListenableFuture/CompletableFuture or composition libraries.
Trying to setup a restful service component that update a database table. Tried using both Spring RestTemplate as well as apache commons restful impl and both seems to no work.
On using
Option 1: Using Spring RestTemplate : Results in following error
com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.LinkedHashMap out of START_ARRAY token
Option 2: Using using org.apache.commons.httpclient.methods.PostMethod; results in following errors
Server side error:
org.codehaus.jackson.JsonParseException: Unexpected character ('<' (code 60)): expected a valid value (number, String, array, object, 'true', 'false' or 'null')
Client side error:
The server refused this request because the request entity is in a format not supported by the requested resource for the requested method ().
My Restful service method is annotated as "Post" and consumes "JSON". My client side controller which initiates the RestFul call, code below
#RequestMapping(value="/update", consumes="application/json")
public void updateMaintReport(
#RequestBody Map<String, String> formModelData,
HttpServletRequest request,
HttpServletResponse response)
throws IOException,JsonMappingException {
logger.log(LogLevel.DEBUG, "REACHED method updateMaintReport..");
System.out.println("Reached method updateMaintReport.....");
boolean errorEncountered = false;
ReportingSession repSession = null;
HttpSession session = request.getSession(false);
if(session==null) {
// TODO: code for handling invalid/expired session
} else {
repSession = (ReportingSession)session.getAttribute(ReportingWebConstants.REPORTING_SESSION);
if(repSession==null) {
errorEncountered = true;
}
}
if(!errorEncountered) {
ServiceClient serviceClient = new ServiceClient();
String servicesUrl = this.environment.getProperty("service_url_reports_data");
String servicesName = this.environment.getProperty("service_name_reports_update_fnol");
String serviceUrl = VIPUrlFactory.getServiceUrl(servicesUrl+servicesName);
logger.log(LogLevel.DEBUG, "For update : serviceUrl: "+serviceUrl);
//Option 1: Using Spring RestTemplate :
LinkedMultiValueMap<String,String> headers = new LinkedMultiValueMap<String,String>();
headers.add("Accept","application/json");
headers.add("Content-type","application/json");
List list = new ArrayList<Map<String, String>>(); list.add(formModelData);
RestTemplate restTemplate = new RestTemplate();
HttpEntity<List> requestEntity = new HttpEntity<List>(list, headers);
ResponseEntity<List> fList = restTemplate.exchange(serviceUrl,
HttpMethod.POST,
requestEntity,
List.class);
//Option 2: using org.apache.commons.httpclient.methods.PostMethod; -- Will be commented when option 1 block is uncommented
serviceClient.setParams(formModelData);
serviceClient.setServiceUrl(serviceUrl);
serviceClient.callRestServicePost();
logger.log(LogLevel.DEBUG, "Posting data to service - to execute the update");
}
}
In the above code, option 1 and option 2 block won't be executed simultaneously.
Below is the code block which accepts the Restful call, my server side code.
#RequestMapping(value = "/update", method = RequestMethod.POST)
public void updateMainRptData(#RequestBody Map<String, String> formModelData) throws ReportingIntegrationException,
IOException, JsonMappingException {
String updateStmt = "UPDATE CL_SCRIPTS SET DELETE_IND = #{delete_ind}, SCRIPT_DESC = #{script_desc}, SCRIPT_TXT = #{script_txt}WHERE COMPANY_CD = #{company_cd} AND SCRIPT_NAME = #{script_name}AND PROMPT_ID = #{prompt_id}";
ParameterObjectDTO paramObjDTO = new ParameterObjectDTO();
logger.log(LogLevel.DEBUG,"In Services Web: updateMainRptData()");
if(!formModelData.isEmpty()) {
Set<String> keySet = formModelData.keySet();
StringBuilder sb = new StringBuilder();
for (String key : keySet) {
sb.append(key).append(" -- ").append(formModelData.get(key)).append("\n");
}
logger.log(LogLevel.DEBUG, sb.toString());
}
paramObjDTO.setModalForQuery(formModelData);
paramObjDTO.setUpdateSqlStmt(updateStmt);
maintReportingSvc.updateMaintReport(paramObjDTO);
}
Error Messages I see in browsers is not helpful but my JSON data is valid I believe. Any help is appreciated. Thanks.
Later I changed the signature of the method updateMainRptData and added a returntype and #ResponseBody to resolve this issue.