I have a spring application that injects certain beans are injexted based on the request context. In this example it is the Facebook bean.
#RestController
#RequestMapping("facebook")
public class FacebookInjectionController {
#Autowired
private Facebook facebook;
#Autowired
private UserRepository userRepository;
#RequestMapping(method = RequestMethod.GET)
public List<String> blah() {
String firstName = facebook.userOperations().getUserProfile().getFirstName();
return Arrays.asList(firstName);
}
#RequestMapping(method = RequestMethod.GET, value = "complex")
public List<String> blah2() {
UserJwt principal = (UserJwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Stream<User> stream = StreamSupport.stream(userRepository.findAll().spliterator(), true);
return stream.filter(u -> u.getUid().equals(principal.getUid()))
.map(u ->
facebook.userOperations().getUserProfile().getFirstName()
).collect(Collectors.toList());
}
}
This code will run normally but every so often it will fail with the following error:
2017-02-09 01:39:59.133 ERROR 40802 --- [o-auto-1-exec-2]
o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for
servlet [dispatcherServlet] in context with path [] threw exception
[Request processing failed; nested exception is
org.springframework.beans.factory.BeanCreationException: Error
creating bean with name 'scopedTarget.facebook': Scope 'request' is
not active for the current thread; consider defining a scoped proxy
for this bean if you intend to refer to it from a singleton; nested
exception is 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.] with root cause
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.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.request.AbstractRequestAttributesScope.get(AbstractRequestAttributesScope.java:41)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:340)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.aop.target.SimpleBeanTargetSource.getTarget(SimpleBeanTargetSource.java:35)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:187)
at com.sun.proxy.$Proxy137.userOperations(Unknown Source)
at com.roomsync.FacebookInjectionController.lambda$blah2$5(FacebookInjectionController.java:43)
at com.roomsync.FacebookInjectionController$$Lambda$10/2024009478.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1374)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:747)
at java.util.stream.ReduceOps$ReduceTask.doLeaf(ReduceOps.java:721)
at java.util.stream.AbstractTask.compute(AbstractTask.java:316)
at java.util.concurrent.CountedCompleter.exec(CountedCompleter.java:731)
at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:902)
at java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1689)
at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1644)
at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
I have tried multiple solutions (including Spring MVC: How to use a request-scoped bean inside a spawned thread?) but none have worked.
Is there a way to pass a request scoped bean down to a lambda or another thread?
going of what https://stackoverflow.com/users/1262865/john16384 said i have changed my config to:
#Bean
#Scope(value = "inheritableThreadScope", proxyMode = ScopedProxyMode.INTERFACES)
public ConnectionRepository connectionRepository(ConnectionFactoryLocator connectionFactoryLocator) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
throw new IllegalStateException("Unable to get a ConnectionRepository: no user signed in");
}
return getUsersConnectionRepository(connectionFactoryLocator).createConnectionRepository(authentication.getName());
}
#Bean
#Scope(value="inheritableThreadScope", proxyMode=ScopedProxyMode.INTERFACES)
public Facebook facebook(ConnectionFactoryLocator connectionFactoryLocator) {
Connection<Facebook> connection = connectionRepository(connectionFactoryLocator).findPrimaryConnection(Facebook.class);
return connection != null ? connection.getApi() : null;
}
#Bean
#Scope(value = "inheritableThreadScope", proxyMode = ScopedProxyMode.INTERFACES)
public ExecutorService fbExecutor () {
return Executors.newSingleThreadExecutor();
}
the controller now looks like:
#RestController
#RequestMapping("facebook")
public class FacebookInjectionController {
#Autowired
private Facebook facebook;
#Autowired
private UserRepository userRepository;
#Autowired
private ExecutorService fbExecutor;
#RequestMapping(method = RequestMethod.GET)
public List<String> blah() {
String firstName = facebook.userOperations().getUserProfile().getFirstName();
return Arrays.asList(firstName);
}
#RequestMapping(method = RequestMethod.GET, value = "complex")
public List<String> blah2() throws ExecutionException, InterruptedException {
UserJwt principal = (UserJwt) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Stream<User> stream = StreamSupport.stream(userRepository.findAll().spliterator(), true);
Future<List<String>> submit = fbExecutor.submit(() -> stream.filter(u -> u.getUid().equals(principal.getUid()))
.map(u ->
facebook.userOperations().getUserProfile().getFirstName()
)
.collect(Collectors.toList()));
return submit.get();
}
}
i also have the following config:
#Configuration
public class BeanFactoryConfig implements BeanFactoryAware {
private static final Logger LOGGER = Logger.getLogger(BeanFactoryConfig.class);
#Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
if (beanFactory instanceof ConfigurableBeanFactory) {
// logger.info("MainConfig is backed by a ConfigurableBeanFactory");
ConfigurableBeanFactory cbf = (ConfigurableBeanFactory) beanFactory;
/*Notice:
*org.springframework.beans.factory.config.Scope
* !=
*org.springframework.context.annotation.Scope
*/
org.springframework.beans.factory.config.Scope simpleThreadScope = new SimpleThreadScope() {
#Override
public void registerDestructionCallback(String name, Runnable callback) {
RequestAttributes attributes = RequestContextHolder.currentRequestAttributes();
attributes.registerDestructionCallback(name, callback, 3);
}
};
cbf.registerScope("inheritableThreadScope", simpleThreadScope);
/*why the following? Because "Spring Social" gets the HTTP request's username from
*SecurityContextHolder.getContext().getAuthentication() ... and this
*by default only has a ThreadLocal strategy...
*also see https://stackoverflow.com/a/3468965/923560
*/
SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);
}
else {
// logger.info("MainConfig is not backed by a ConfigurableBeanFactory");
}
}
}
even with this it sometimes get the error:
{
"timestamp": 1486686875535,
"status": 500,
"error": "Internal Server Error",
"exception": "java.util.concurrent.ExecutionException",
"message": "org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.facebook' defined in class path resource [com/roomsync/config/SocialConfig.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.social.facebook.api.Facebook]: Factory method 'facebook' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.connectionRepository': Scope 'inheritableThreadScope' is not active for the current thread; consider defining a scoped proxy for this bean if you intend to refer to it from a singleton; nested exception is 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.",
"path": "/facebook/complex"
}
so it seems that im still missing the piece to activate the scope and copying the thread local context to it
There's two things going on:
1) Java streams use a common Fork/Join pool to execute things in parallel. These threads are not created by the Spring framework (or by you).
2) Request scoped beans are supported by using a ThreadLocal.
This means that if a thread, not created by Spring, tries to access a request scoped bean, it won't be found as the thread does not know about it (it is not in the ThreadLocal).
In order for you to resolve this issue you will need to take control of which threads are used for your streams. Once you achieved that, you can make a copy of the request scoped beans to use for the sub-threads. You'll also need to clean them up again after the thread has finished its task or you risk leaving beans behind that may be seen by the next task being executed on that thread.
To change which threads are used by parallel streams, see: Custom thread pool in Java 8 parallel stream
How to configure Spring properly to propagate request scoped beans to child threads you already found I think.
This is what worked for me to transfer request beans in fork-joined threads. The example is only for illustration.
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
// org.slf4j:slf4j-api:1.7.30
import org.slf4j.MDC;
// org.springframework:spring-web:5.2.12.RELEASE
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
class Scratch {
public static void main(String[] args) {
RequestAttributes context = RequestContextHolder.currentRequestAttributes();
Map<String, String> contextMap = MDC.getCopyOfContextMap();
List<String> list = new ArrayList<>();
list.parallelStream().map(id -> {
try {
// copy all required for spring beans
RequestContextHolder.setRequestAttributes(context);
MDC.setContextMap(contextMap);
// ************************************
// Spring request beans usage goes here
// ************************************
return 1;
} finally {
// clean all from thread local
MDC.clear();
RequestContextHolder.resetRequestAttributes();
}
})
.collect(Collectors.toList());
}
}
Is it required, that the stream is processed in parallel? That causes, that the lambda may be executed in another thread.
Stream stream = StreamSupport.stream(userRepository.findAll().spliterator(), false);
I had the same issue, I was trying to use the parallel stream to fetch job information from Kubernetes REST API since the parallel stream uses new Threads as John16384 explained, my code couldn't get the 'scopedTarget.oauth2ClientContext' because it's scope is request in Spring and the thread created by parallel stream couldn't access it. So I had to change it like below;
old version: items.parallelStream().map(jobItem -> createJobObject(jobItem, createJobTrigger(jobItem))).collect(Collectors.toList());
fixed version: items.stream().map(jobItem -> createJobObject(jobItem, createJobTrigger(jobItem))).collect(Collectors.toList());
and inside the createJobObject method, I was calling a REST service
restTemplate.getForEntity(url, KubernetesJob.class).getBody().getItems();
Related
I have to build a Spring Batch system. In my project I have to call, inside a batch scheduled, an Api method defined in Controller class of my project.
This is an example of controller
#RestController
public class MyController implements ExampleApi {
// other methods
#Override
public ResponseEntity<ExampleResponse> method(String object){
final ExampleResponse response = // execution
return ResponseEntity.status(HttpStatus.OK).body(response);
}
}
I call the method above in my CustomProcessor. This is an example of Batch processor
public class CustomProcessor implements ItemProcessor<List<String>, List<String>> {
#Autowired
private ExampleApi exampleApi;
#Override
public List<String> process(#NonNull List<String> objects) throws Exception {
objects.forEach(object -> exampleApi.method(object));
return objects;
}
}
When batch starts running I can call the api, but when I call inside another api in another class annotated like this:
#Component
#Scope(BeanDefinition.SCOPE_PROTOTYPE)
public class ObjectFactoryImpl implements ObjectFactory {
#Autowired
#Qualifier("anotherService")
private ObjectProvider<AnotherApi> anotherApiObjectProvider;
public Object find(Long objectId) {
Object object;
try {
object = anotherApiObjectProvider.getObject().getObjectById(objectId).getObject();
} catch (Exception e) {
throw new Exception....
}
}
I get this exception:
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:
In this case, use RequestContextListener or RequestContextFilter to
expose the current request.
I'm quite new to Spring Batch.
After adding the Hystrix command am getting the following error -
Error executing HystrixCommand.run(). Proceeding to fallback logic ...: 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: In this case, use RequestContextListener or RequestContextFilter to expose the current request. at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131) at
My application class looks like this -
#SpringBootApplication
#EnableDistributedSession(regionName="DATASERVICES")
#EnableDiscoveryClient
#EnableCircuitBreaker
public class SampleApplication {
public static void main(String[] args) {
SpringApplication.run(SampleApplication.class, args);
}
}
I have seen the error is happening at the line where we are trying to get the session in Service class. My service class where I have implemented the Hystrix looks like this -
#Service
public class TransactionsUSStandardServiceImpl implements TransactionsUSStandardService {
#Autowired
private RestTemplate restTemplate;
#Autowired
HttpServletRequest request;
#Override
#HystrixCommand(commandKey = "getXyzCommand", threadPoolKey = "getXyzThreadPoolKey", fallbackMethod = "xyzFallback")
public List<String> getXyz(String arg1, HttpHeaders headers, String arg2) throws Exception {
HttpSession session = request.getSession(false);
...
...
}
public List<String> xyzFallback(String arg1, HttpHeaders headers, String arg2, Throwable t) throws Exception {
LOGGER.error(".......");
throw new XyzException("Hystrix CircuitBreaker Fallback while executing getXyz() method", t);
}
}
In the SVN we kept our configuration as -
hystrix:
command:
defaultHystrixCommand:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
getXyzCommand:
execution:
isolation:
thread:
timeoutInMilliseconds: 30000
threadpool:
getXyzThreadPoolKey:
coreSize: 20
maximumSize: 20
maxQueueSize: 5
Am I missing anything in my Hystrix configuration?
I am calling different other web services from my spring boot application. To increase the performance rather than calling them sequentially I wanted to implement CompletableFuture.supplyAsync() to have an asynchronous execution.
The services which i am calling, some of them are internal and some are external. Therefore, for internal I am directly calling their api interfaces which are present in maven dependency and for external ones i am using javax.ws.rs.client.WebTarget
While implementing the CompletableFuture.supplyAsync() call the client classes which are internal gets executed successfully while those which have WebTarget as #Inject dependency fails as the scopedTarget.XXService_nameXX_wt bean is not created.
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.charityserv_wt': Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.ws.rs.client.WebTarget]: Factory method 'target' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'scopedTarget.coreContext': Scope 'request' is not active for the current thread
Calling code:
CompletableFuture<SearchResult> future = CompletableFuture.supplyAsync(() -> searchServiceClientImpl.getsearchSearchRequest(encryptedAccountNumber));
ServiceClientClass:
#Component
public class SearchServiceClientImpl{
public static final Logger LOGGER = LoggerFactory.getLogger(SearchServiceClientImpl.class);
#Inject
#EndPoint(service = "searchserv")
private WebTarget searchserv;
/**
#Inject
#EndPoint(service = "searchserv")
private SearchResource searchserv;
**/
public Response buildsearchRequest(String payerId) throws WebApplicationException , IllegalArgumentException,ProcessingException {
Response response = null;
String apiUrl = "path/url";
response = searchserv.path(apiUrl).request(MediaType.APPLICATION_JSON).get();
return response;
}
public SearchSearchResult getsearchSearchRequest(String encryptedAccountNumber){
SearchSearchResult SearchSearchResult = null;
Response response = null;
try{
response = buildSearchRequest(encryptedAccountNumber);
if (null == response){
return null;
}
if(response.getStatus() == Response.Status.OK.getStatusCode()) {
SearchSearchResult = response.readEntity(SearchSearchResult.class);
}
return SearchSearchResult;
}
The dependency injection SearchResource searchserv which is mentioned in comments works but WebTarget causes this exception. Is there any specific reason for this?
This question already has an answer here:
Accessing HttpSession outside of the originally receiving thread
(1 answer)
Closed 6 years ago.
I have a fully-annotation-driven Spring Boot 1.3.5 app which has this asynchronous service which needs to autowire another service bean (And in the future it will need to autowire a repository bean, but I'm not there yet) in order to perform some business logic:
#Service
public class AsyncService {
#Autowired
public HelpingService helpingService;
#Async
public Future<String> doFoo(String someArgument)
throws InterruptedException {
Thread.sleep(3000);
System.out.println("about to do Foo "+someArgument);
String result = "";
try {
result = helpingService.getSomeStuff(someArgument);
}
catch (Exception e) {
e.printStackTrace();
}
return new AsyncResult<String>(hello);
}
}
That method above is being called from a #Controller bean, which has other endpoints (Non-async) that work as expected also using this
#Controller
public class MyController extends BaseController {
#Autowired
HelpingService helpingService;
#Autowired
AsyncService asyncService;
#RequestMapping(method=RequestMethod.GET, value={"/rest/threads/getIp/{jobId}"}, produces={"application/json"})
public ResponseEntity<?> getLog(#PathVariable("jobId") String jobId) throws InterruptedException {
asyncService.doFoo(jobId);
return new ResponseEntity<>(HttpStatus.OK);
}
}
And here's helpingService's implementation (It's an interface), calling any method works perfectly fine when I'm not doing it from the #Async method above:
#Service
#Validated
public class HelpingServiceImpl implements HelpingService {
#Autowired
HttpSession httpSession;
#Value(value="${projName}")
private String projName;
public String getServerAddress(){
AuthRegion region = (AuthRegion) httpSession.getAttribute("region");
if (region != null)
return region.getServerAddress();
else
return null;
}
#Override
public String getSomeStuff(String jobId) {
String responseString = "";
String projName = this.projName;
String serverAddress = getServerAddress(); // Code stops here with an exception
// Some code here that works fine outside this thread
return responseString;
}
}
This is the exception being caught:
about to do Foo (267)
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.
at org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes(RequestContextHolder.java:131)
at org.springframework.web.context.support.WebApplicationContextUtils.currentRequestAttributes(WebApplicationContextUtils.java:309)
at org.springframework.web.context.support.WebApplicationContextUtils.access$400(WebApplicationContextUtils.java:64)
at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:366)
at org.springframework.web.context.support.WebApplicationContextUtils$SessionObjectFactory.getObject(WebApplicationContextUtils.java:361)
at org.springframework.beans.factory.support.AutowireUtils$ObjectFactoryDelegatingInvocationHandler.invoke(AutowireUtils.java:307)
at com.sun.proxy.$Proxy96.getAttribute(Unknown Source)
at corp.fernandopcg.myapp.service.ThreadServiceImpl.getRundeckServerPort(ThreadServiceImpl.java:45)
at corp.fernandopcg.myapp.service.ThreadServiceImpl.getJobExecutionOutput(ThreadServiceImpl.java:65)
at corp.fernandopcg.myapp.service.AsyncService.doFoo(AsyncService.java:40)
at corp.fernandopcg.myapp.service.AsyncService$$FastClassBySpringCGLIB$$7e164220.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:720)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:157)
at org.springframework.aop.interceptor.AsyncExecutionInterceptor$1.call(AsyncExecutionInterceptor.java:115)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
I added (With some changes as I couldn't extend AsyncConfigurer at the same time as SpringBootServletInitializer, and I had to catch an exception not mentiones there) the taskExecutor part to my Application main class as follows, guided by this tutorial which does look similar to what I need, in my opinion
#SpringBootApplication
#EnableAsync
#EnableJpaRepositories(repositoryFactoryBeanClass = DataTablesRepositoryFactoryBean.class)
public class MyApplication extends SpringBootServletInitializer implements AsyncConfigurer{
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
#Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(2);
executor.setMaxPoolSize(2);
executor.setQueueCapacity(500);
executor.setThreadNamePrefix("SomeRandomLookup-");
executor.initialize();
return executor;
}
#Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
// TODO Auto-generated method stub
return null;
}
}
Can I tell my #Async service to be able to use other services of the application? Because if that's not possible, I don't really see the use of these threading mechanism.
This is a great illustration of why request-scope injection can be problematic. Your HelpingServiceImpl has a hidden dependency on the request-specific HttpSession, which looks like a field but is actually a proxy that is resolved by Spring on each call to always refer to the "current" request (using a thread-local variable).
The problem is that by making your call #Async, you're separating the HelpingServiceImpl invocation from the request that triggered it, and there's no longer the implicit connection of being on the same thread that would allow it to pull information from the globalish context.
The most straightforward fix is to make your dependencies explicit--instead of having your HelpingServiceImpl grab the region directly off of the HttpSession, pass the region to it as a method parameter.
I need to run a JUnit vs Spring MVC test case in which preconditions include that certain data is present in the HTTP Session. Most important is that I cannot wire a session-scoped bean: I must access httpServletContext.getSession().
Before showing code, let me explain. The controller I need to test assumes that a certain datum is stored in session, otherwise throws an exception. And that is the correct behaviour for now, because that controller is never invoked without a session and the session is always initialized with application data at login time. And obviously the controller is under security.
In my test, I just need to test whether this controller returns either a redirection or a 404 not found according to the request parameters.
I thought building my test case such as
#Autowired
private HttpServletRequest httpServletRequest;
#Autowired
private ModuleManager moduleManager;
#Autowired
private WebApplicationContext webApplicationContext;
private MenuItem rootMenu;
private MockMvc mockMvc;
#Before
public void setUp() throws Exception
{
mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
// No asserzioni
.build();
rootMenu = moduleManager.getRootMenu()
.clone();
httpServletRequest.getSession()
.setAttribute(MenuItem.SESSION_KEY, rootMenu);
assertNotNull(rootMenu.getDescendant(existingSelectedMenu));
assertNull(rootMenu.getDescendant(notExistingMenu));
}
#Test
public void testNavigate() throws Exception
{
mockMvc.perform(get("/common/navigate?target=" + existingSelectedMenu))
.andExpect(status().is3xxRedirection());
assertNotSelected(rootMenu, existingSelectedMenu);
mockMvc.perform(get("/common/navigate?target=" + notExistingMenu))
.andExpect(status().is4xxClientError());
}
Part of the code is truly self-explaining. Anyway I expect /common/navigate to use the value I stored in the session. Like this
#RequestMapping(value = "/common/navigate",
method = RequestMethod.GET)
public String navigate(#RequestParam("target") String target) throws NotFoundException
{
MenuItem rootMenu = (MenuItem) httpServletRequest.getSession()
.getAttribute(MenuItem.SESSION_KEY);
if (rootMenu == null)
throw new RuntimeException("Menu not found in session"); //Never happens
MenuItem menuItem = rootMenu.getAndSelect(target);
if (menuItem == null)
throw new NotFoundException(MenuItem.class, target); //Expected
return "redirect:" + menuItem.getUrl();
}
Now guess. What happens when I run my code?
RuntimeException is thrown in the line I commented as the menu object is not found in the session
Obviously the question is implicit now, but I will still write it: how do I inject data into the Session object so that controllers under test will have them available as precondition?
Found the solution by myself now.
The problem is that the session itself must be mocked too. Spring provides a MockHttpSession class that does the trick. It can be pre-populated with all pre-conditions, but must be passed to every MockMvc request so that the mock will wire the session to the (mocked) servlet context.
Following code initializes the session
mockHttpSession = new MockHttpSession(webApplicationContext.getServletContext());
mockHttpSession.setAttribute(MenuItem.SESSION_KEY, rootMenu);
Following performs the request with mocked session wired to it
mockMvc.perform(get("/common/navigate?target=" + existingSelectedMenu).session(mockHttpSession))
.andExpect(status().is3xxRedirection());