How to set priority in ExceptionHandling via ControllerAdvice - spring

I was implement 2 ControllersAdvice to. handle exception
CommonAdvice and UserAdvice
Common Advice
#ControllerAdvice(annotations = RestController.class)
public class CommonAdvice {
#ExceptionHandler(Exception.class)
public ResponseEntity<ExceptionBean> handleException(Exception e) {
ExceptionBean exception = new ExceptionBean(Causes.ANOTHER_CAUSE);
return new ResponseEntity<ExceptionBean>(exception, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
UserAdvice
#ControllerAdvice(assignableTypes = { requestUserMapper.class })
public class UserAdvice {
#ExceptionHandler(NotUniqueUserLoginException.class)
public ResponseEntity<ExceptionBean> handleAlreadyFound(NotUniqueUserLoginException e) {
System.out.println("this is me : " + Causes.USER_ALREADY_EXIST.toString());
ExceptionBean exception = new ExceptionBean(Causes.USER_ALREADY_EXIST);
return new ResponseEntity<ExceptionBean>(exception, HttpStatus.INTERNAL_SERVER_ERROR);
}
And now, when I throw NotUniqueUserException, this is a CommonAdvice which handle and exception.
I tested and UserAdvice works fine.
There is the way to set priority on this classes ?
#Edit - add Controllel Mapping
#RequestMapping(value = "add", method = RequestMethod.POST)
public ResponseEntity<GT_User> addUser(#RequestBody GT_User newUser) throws NotUniqueUserLoginException, Exception {
if (this.userService.exist(newUser.getLogin())) {
throw new NotUniqueUserLoginException(Causes.USER_ALREADY_EXIST.toString());
} else {
GT_User addesUser = this.userService.addUser(newUser);
return new ResponseEntity<GT_User>(addesUser, HttpStatus.OK);
}
}

To set Higher priority to an ControllerAdvice on add :
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import com.genealogytree.webapplication.dispatchers.requestUserMapper;
#ControllerAdvice(assignableTypes = { requestUserMapper.class })
#Order(Ordered.HIGHEST_PRECEDENCE)
public class UserAdvice {
...
}
To set Lower priority to an ControolerAdvice on add
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import com.genealogytree.webapplication.dispatchers.requestUserMapper;
#ControllerAdvice(assignableTypes = { requestUserMapper.class })
#Order(Ordered.LOWEST_PRECEDENCE)
public class CommonAdvice {
...
}

Related

How to implement asynchronous retry mechanism in java 8?

I'm using Java 8 Spring boot. I have below method.
public hello() {
try {
// send message
}
catch(HttpClientErrorException e) {
if (e.getRawStatusCode() == 401) {
// I need to retry the same hello() method for three times as in 10sec, 20sec and 25sec.
}
}
}
I need to call the same method three times for retrying whenever it hits the catch block.
How can I do this asynchronously?
I found below code but it didn't work.
#Retryable( value = {RestClientException.class}, maxAttempts = 3, backoff = #Backoff(3000))
Appreciate your help.
You can use #Async annotation from Spring to achieve that.
You have to create a config like this:
#Configuration
#EnableRetry
#EnableAsync
class RetryConfig {}
When you want to use Async with Retry you have to decorate the method with Async which is trying to call a Retryable method. Also, you have to make sure that you are returning Future<> or similar because you are sending that piece of code for a toss in the background
I have also implemented fallback mechanism otherwise the request will terminate with 500 exception.
If you run the code below you can see that the main request is executed on thread http-nio-8080-exec-1 while your Async code is executed on a different thread task-1.
I tried to explain this with a sample service method, but the concept will be same for local or remote service call.
A detailed exmaple is given below:
package com.example.silentsudo.springcloudssamples;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Configuration;
import org.springframework.retry.annotation.Backoff;
import org.springframework.retry.annotation.EnableRetry;
import org.springframework.retry.annotation.Recover;
import org.springframework.retry.annotation.Retryable;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.concurrent.CompletableFuture;
#SpringBootApplication
public class SpringCloudsSamplesApplication {
public static void main(String[] args) {
SpringApplication.run(SpringCloudsSamplesApplication.class, args);
}
}
#RequestMapping(path = "sample")
#RestController
class SampleController {
private final GreetService greetService;
SampleController(GreetService greetService) {
this.greetService = greetService;
}
#GetMapping
public String hello() {
System.out.println(Thread.currentThread().getName());
return "Hello!";
}
#GetMapping(path = "greet")
public String greet(#RequestParam(value = "name", defaultValue = "John") String name) {
return greetService.greet(name);
}
#Async
#GetMapping(path = "greet-async")
public CompletableFuture<String> greetAsync(#RequestParam(value = "name", defaultValue = "John") String name) {
return CompletableFuture.completedFuture(greetService.greet(name));
}
}
#Configuration
#EnableRetry
#EnableAsync
class RetryConfig {
}
#Service
class GreetService {
private final UngaBungaService ungaBungaService;
GreetService(UngaBungaService ungaBungaService) {
this.ungaBungaService = ungaBungaService;
}
#Retryable(maxAttempts = 5, value = GreetException.class, backoff = #Backoff(value = 3000L))
public String greet(String name) {
return ungaBungaService.lol(name);
}
#Recover
public String recoverGreetException(GreetException greetException) {
return greetException.getMessage();
}
}
#Service
class UngaBungaService {
public String lol(String name) {
System.out.println(Thread.currentThread().getName());
throw new GreetException("Called greet for " + name);
}
}
class GreetException extends RuntimeException {
public GreetException(String message) {
super(message);
}
}
For retry mechanisms, you can to use the #Retryable(value = RestClientException.class)
For this to trigger, you need to actually throw this exception (or something that extends from RestClientException). Because of your catch statement, no exception is actually thrown, so the retry mechanism doesn't kick in.
#Retryable( value = {RestClientException.class}, maxAttempts = 3, backoff = #Backoff(3000))
public void hello() {
try {
// send message
}
catch(HttpClientErrorException e) {
if (e.getRawStatusCode() == 401) {
throw new RestClientException("meaningfull message");
}
}
}
If you want to run some catch code after the 3 retries failed, you can make use of the #Recover annotation on a recovery method.
If you want some more info on the retry mechanism, you could look here
Also don't forget to add #EnableRetry in your config so that the annotations are used.
Full code example with spring boot
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(DemoApplication.class, args);
context.getBean(TestService.class).hello();
context.close();
}
#Configuration
#EnableRetry
public class AppConfig {
}
#Service
public class TestService {
#Retryable(value = {IllegalArgumentException.class}, maxAttempts = 4, backoff = #Backoff(delay = 1000, multiplier = 4))
public void hello() {
try {
int a = Integer.parseInt(null);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Exception triggered");
}
}
}

How can I test a my rest controller that has feign clients?

I have a rest controller that is using 2 fein clients, I want to write and test the Rest controller with different samples, I am not an expert at writing springboot tests.
In this scenario I have no repositories to test with, just feign clients accessed through a rest controller. Below is my testing controller code
#RestController
public class CustomerController {
#Autowired
private CustomerClient customerClient;
#Autowired
private PaymentsClient paymentsClient;
#RequestMapping(path = "/getAllCustomers", method = RequestMethod.GET)
public ResponseEntity<Object> getAllCustomers() {
List<Customer> customers = customerClient.getAllCustomers();
return new ResponseEntity<>(customers, HttpStatus.OK);
}
#RequestMapping(path = "/{customerId}", method = RequestMethod.GET)
public ResponseEntity<Object> get(#PathVariable() long customerId) {
try {
Customer c = customerClient.getCustomerById(customerId);
if (c != null) {
return new ResponseEntity<>(c, HttpStatus.OK);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Customer Not Found");
}
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
#RequestMapping(path = "/{customerId}", method = RequestMethod.PATCH)
public ResponseEntity<Object> UpdateCustomer(#PathVariable() Long customerId, #RequestBody Customer customer) {
Customer c;
try {
c = customerClient.update(customerId, customer);
if (c != null) {
return new ResponseEntity<>(c, HttpStatus.OK);
} else {
return ResponseEntity.status(HttpStatus.NOT_FOUND).body("Customer Not Found");
}
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
#RequestMapping(path = "", method = RequestMethod.POST)
public ResponseEntity<Object> saveCustomer(#RequestBody Customer customer) {
Customer c;
try {
c = customerClient.saveCustomer(customer);
return new ResponseEntity<>(c, HttpStatus.OK);
} catch (Exception e) {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
#RequestMapping(path = "/registerPayment", method = RequestMethod.POST)
public ResponseEntity<Object> saveCustomer(#RequestBody Payment payment) {
Payment p = null;
Customer c = null;
try {
c = customerClient.getCustomerById(payment.getCustomerId());
p = paymentsClient.saveCustomer(payment);
return new ResponseEntity<>(p, HttpStatus.OK);
} catch (Exception e) {
if (null == c) {
return ResponseEntity.status(HttpStatus.UNPROCESSABLE_ENTITY).body("Customer Does not Exist");
} else {
e.printStackTrace();
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
}
}
}
Most of the tests I have seen have a repository injected in but for my case
I dont have those, just fein clients, Im I doing it wrong,
Below is my current test
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class CustomerControllerTest {
#Autowired
private MockMvc mockMvc;
#InjectMocks
private CustomerController customerController;
#Before
public void setup() {
mockMvc = MockMvcBuilders.standaloneSetup(customerController).build();
}
#Test
public void getAllCustomers() {
try {
this.mockMvc.perform(get("/getAllCustomers")).andExpect(status().isOk())
.andExpect(content().json("[{\n" + " \"customerId\": 24,\n"
+ " \"firstName\": \"Benjamin\",\n" + " \"secondName\": \" Masiga\",\n"
+ " \"email\": \"ben#ben.com\"\n" + " }"));
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
I'm getting the error below,
NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.test.web.servlet.MockMvc' available: expected at least 1 bean which qualifies as autowire candidate.
Its as simple as writing any other JUnit test.
#RunWith(SpringRunner.class)
#SpringBootTest
#ActiveProfiles("test")
public class CustomerControllerTest {
#Mock
private CustomerClient customerClient;
#InjectMocks
private CustomerController customerController;
#Test
public void getAllCustomers() {
List<Customer> customers = new ArrayList<>();
customers.add(new Customers("name"));
Mockito.when(customerClient.getAllCustomers()).thenReturn(customers);
Mockito.assertEquals(customers.toString(),customerController.getAllCustomers())
}
}
You need your own feign.Client that works beyond MockMvc:
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.function.Function.identity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.request;
import feign.Client;
import feign.Request;
import feign.Response;
import java.io.UncheckedIOException;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
public class MockMvcFeignClient implements Client {
private final MockMvc mockMvc;
public MockMvcFeignClient(MockMvc mockMvc) {
this.mockMvc = mockMvc;
}
#Override
public Response execute(Request request, Request.Options options) {
URI requestUrl = URI.create(request.url());
List<String> uriVars = new ArrayList<>();
String urlTemplate = fillVarsAndGetUrlTemplate(requestUrl, uriVars);
HttpMethod method = HttpMethod.valueOf(request.method());
byte[] body = request.body();
HttpHeaders httpHeaders = convertHeaders(request);
MockHttpServletRequestBuilder requestBuilder = request(method, urlTemplate, uriVars.toArray())
.headers(httpHeaders)
.content(body);
MockHttpServletResponse resp;
try {
ResultActions resultActions = mockMvc.perform(requestBuilder);
resp = resultActions.andReturn()
.getResponse();
} catch (Exception e) {
throw new IllegalStateException("Error while executing request", e);
}
return convertResponse(request, resp);
}
static String fillVarsAndGetUrlTemplate(URI requestUrl, List<String> uriVars) {
StringBuilder urlTemplate = new StringBuilder(requestUrl.getPath());
if (requestUrl.getQuery() != null) {
urlTemplate.append('?');
String[] pairs = requestUrl.getRawQuery().split("&");
for (int i = 0; i < pairs.length; i++) {
String pair = pairs[i];
int separator = pair.indexOf('=');
String paramName;
String paramValue;
if (separator < 0) {
paramName = pair;
paramValue = null;
} else {
paramName = pair.substring(0, separator);
try {
paramValue = URLDecoder.decode(pair.substring(separator + 1), UTF_8.name());
} catch (UnsupportedEncodingException e) {
throw new UncheckedIOException(e);
}
}
urlTemplate.append(i == 0 ? paramName : "&" + paramName);
if (paramValue != null) {
urlTemplate.append("={").append(paramName).append('}');
uriVars.add(paramValue);
}
}
}
return urlTemplate.toString();
}
private static HttpHeaders convertHeaders(Request request) {
HttpHeaders headers = new HttpHeaders();
request.headers().forEach((header, values) -> headers.put(header, new ArrayList<>(values)));
return headers;
}
private static Response convertResponse(Request request, MockHttpServletResponse resp) {
return Response.builder()
.request(request)
.status(resp.getStatus())
.body(resp.getContentAsByteArray())
.headers(resp.getHeaderNames().stream()
.collect(Collectors.toMap(identity(), resp::getHeaders)))
.build();
}
}
and create your feign client with it:
return Feign.builder()
.client(new MockMvcFeignClient(mockMvc))
...
So for tests where you could make MockMvc integration tests, now you can call it via feign client.

How to use Apache CachingHttpAsyncClient with Spring AsyncRestTemplate?

Is it possible to use CachingHttpAsyncClient with AsyncRestTemplate? HttpComponentsAsyncClientHttpRequestFactory expects a CloseableHttpAsyncClient but CachingHttpAsyncClient does not extend it.
This is known as issue SPR-15664 for versions up to 4.3.9 and 5.0.RC2 - fixed in 4.3.10 and 5.0.RC3. The only way around is is creating a custom AsyncClientHttpRequestFactory implementation that is based on the existing HttpComponentsAsyncClientHttpRequestFactory:
// package required for HttpComponentsAsyncClientHttpRequest visibility
package org.springframework.http.client;
import java.io.IOException;
import java.net.URI;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.Configurable;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.client.cache.CacheConfig;
import org.apache.http.impl.client.cache.CachingHttpAsyncClient;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.apache.http.protocol.HttpContext;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.http.HttpMethod;
import org.springframework.util.Assert;
// TODO add support for other CachingHttpAsyncClient otpions, e.g. HttpCacheStorage
public class HttpComponentsCachingAsyncClientHttpRequestFactory extends HttpComponentsClientHttpRequestFactory implements AsyncClientHttpRequestFactory, InitializingBean {
private final CloseableHttpAsyncClient wrappedHttpAsyncClient;
private final CachingHttpAsyncClient cachingHttpAsyncClient;
public HttpComponentsCachingAsyncClientHttpRequestFactory() {
this(HttpAsyncClients.createDefault(), CacheConfig.DEFAULT);
}
public HttpComponentsCachingAsyncClientHttpRequestFactory(final CacheConfig config) {
this(HttpAsyncClients.createDefault(), config);
}
public HttpComponentsCachingAsyncClientHttpRequestFactory(final CloseableHttpAsyncClient client) {
this(client, CacheConfig.DEFAULT);
}
public HttpComponentsCachingAsyncClientHttpRequestFactory(final CloseableHttpAsyncClient client, final CacheConfig config) {
Assert.notNull(client, "HttpAsyncClient must not be null");
wrappedHttpAsyncClient = client;
cachingHttpAsyncClient = new CachingHttpAsyncClient(client, config);
}
#Override
public void afterPropertiesSet() {
startAsyncClient();
}
private void startAsyncClient() {
if (!wrappedHttpAsyncClient.isRunning()) {
wrappedHttpAsyncClient.start();
}
}
#Override
public ClientHttpRequest createRequest(final URI uri, final HttpMethod httpMethod) throws IOException {
throw new IllegalStateException("Synchronous execution not supported");
}
#Override
public AsyncClientHttpRequest createAsyncRequest(final URI uri, final HttpMethod httpMethod) throws IOException {
startAsyncClient();
final HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri);
postProcessHttpRequest(httpRequest);
HttpContext context = createHttpContext(httpMethod, uri);
if (context == null) {
context = HttpClientContext.create();
}
// Request configuration not set in the context
if (context.getAttribute(HttpClientContext.REQUEST_CONFIG) == null) {
// Use request configuration given by the user, when available
RequestConfig config = null;
if (httpRequest instanceof Configurable) {
config = ((Configurable) httpRequest).getConfig();
}
if (config == null) {
config = createRequestConfig(cachingHttpAsyncClient);
}
if (config != null) {
context.setAttribute(HttpClientContext.REQUEST_CONFIG, config);
}
}
return new HttpComponentsAsyncClientHttpRequest(cachingHttpAsyncClient, httpRequest, context);
}
#Override
public void destroy() throws Exception {
try {
super.destroy();
} finally {
wrappedHttpAsyncClient.close();
}
}
}

how can i use spring framework with lucene

all.
i am a newbie of lucene, and i'm using spring-mvc (3.2.5.RELEASE) and lucene(4.6.0).
both are newest version currently.
how can i use NEAR REAL TIME search?
i write this code to get instance of IndexWriter (sington)
package com.github.yingzhuo.mycar.search;
import java.io.IOException;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.IndexWriterConfig;
import org.apache.lucene.store.Directory;
import org.apache.lucene.store.FSDirectory;
import org.apache.lucene.util.Version;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.core.io.Resource;
import org.springframework.util.Assert;
import org.wltea.analyzer.lucene.IKAnalyzer;
public class IndexWriterFactoryBean implements FactoryBean<IndexWriter>, InitializingBean, DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(IndexWriterFactoryBean.class);
private Analyzer analyzer = new IKAnalyzer(false);
private Resource indexDirectory = null;
private IndexWriter indexWriter = null;
private Directory directory = null;
public IndexWriterFactoryBean() {
if (indexDirectory != null) {
try {
if (! indexDirectory.getFile().exists()) {
FileUtils.forceMkdir(indexDirectory.getFile());
}
} catch (IOException e) {
LOGGER.warn(e.getMessage(), e);
}
}
}
#Override
public IndexWriter getObject() throws Exception {
return indexWriter;
}
#Override
public Class<?> getObjectType() {
return IndexWriter.class;
}
#Override
public boolean isSingleton() {
return true;
}
#Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(analyzer, "property 'analyzer' must be set.");
Assert.notNull(indexDirectory, "property 'indexDirectory' must be set.");
directory = FSDirectory.open(indexDirectory.getFile());
indexWriter = new IndexWriter(directory, new IndexWriterConfig(Version.LUCENE_46, analyzer));
}
#Override
public void destroy() throws Exception {
IOUtils.closeQuietly(indexWriter);
IOUtils.closeQuietly(directory);
IOUtils.closeQuietly(analyzer);
}
// getter & setter
// ------------------------------------------------------------------------------------------
public void setAnalyzer(Analyzer analyzer) {
this.analyzer = analyzer;
}
public void setIndexDirectory(Resource indexDirectory) {
this.indexDirectory = indexDirectory;
}
}
and this utility to get DirectoryReader by static method.
import java.io.IOException;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.index.IndexWriter;
import com.github.yingzhuo.mycar.config.SpringUtils;
public final class DirectoryReaderHolder {
private static DirectoryReader HOLDER = null;
public synchronized static DirectoryReader get() {
if (HOLDER == null) {
try {
HOLDER = DirectoryReader.open(SpringUtils.getBean(IndexWriter.class), true);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
return HOLDER;
}
public static synchronized void set(DirectoryReader directoryReader) {
if (directoryReader == null) {
throw new NullPointerException();
} else {
HOLDER = directoryReader;
}
}
}
and this bean to inject into my spring-mvc controller. In 'create' method, i am trying to get a new reader before i create a IndexSearcher, but HOW SHOULD I HANDLE THE OLD READER ?
can i close it directly? if other threads are still using the old reader, very bad thing will happen ?
package com.github.yingzhuo.mycar.search;
import java.io.IOException;
import org.apache.lucene.index.DirectoryReader;
import org.apache.lucene.search.IndexSearcher;
public class IndexSearcherManager {
public IndexSearcher create() {
try {
DirectoryReader oldReader = DirectoryReaderHolder.get();
DirectoryReader newReader = DirectoryReader.openIfChanged(oldReader);
if (newReader != null) {
oldReader.close(); // AM I RIGHT ???
oldReader = newReader;
}
return new IndexSearcher(oldReader);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}
Any suggestions? thank you.

Spring/JSF2 and #ViewScoped

I want to use spring managed beans for my JSF2 controllers so that autowiring works. I know that there is no #ViewScoped in spring and I know a few implementations of #ViewScoped floating around various blogs (one from the primefaces lead).
Is any of them used in a real application and considered stable? Maybe one of them is recommended or widely used and I'm just not able to find it.
There is one :) Without memory leaks and with #PreDestroy support. Tested in production. Here.
package org.nkey.primefaces.scopes.test.spring.scope;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.ObjectFactory;
import org.springframework.beans.factory.config.Scope;
import javax.faces.component.UIViewRoot;
import javax.faces.context.FacesContext;
import javax.faces.event.PreDestroyViewMapEvent;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;
import java.io.Serializable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
/**
* #author m.nikolaev Date: 21.11.12 Time: 0:37
*/
public class ViewScope implements Scope, Serializable, HttpSessionBindingListener {
private static final Logger LOGGER = LoggerFactory.getLogger(ViewScope.class);
private final WeakHashMap<HttpSession, Set<ViewScopeViewMapListener>> sessionToListeners = new WeakHashMap<>();
#Override
public Object get(String name, ObjectFactory objectFactory) {
Map<String, Object> viewMap = FacesContext.getCurrentInstance().getViewRoot().getViewMap();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (viewMap) {
if (viewMap.containsKey(name)) {
return viewMap.get(name);
} else {
LOGGER.debug("Creating bean {}", name);
Object object = objectFactory.getObject();
viewMap.put(name, object);
return object;
}
}
}
#Override
public Object remove(String name) {
throw new UnsupportedOperationException();
}
#Override
public String getConversationId() {
return null;
}
#Override
public void registerDestructionCallback(String name, Runnable callback) {
LOGGER.debug("registerDestructionCallback for bean {}", name);
UIViewRoot viewRoot = FacesContext.getCurrentInstance().getViewRoot();
ViewScopeViewMapListener listener =
new ViewScopeViewMapListener(viewRoot, name, callback, this);
viewRoot.subscribeToViewEvent(PreDestroyViewMapEvent.class, listener);
HttpSession httpSession = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(true);
final Set<ViewScopeViewMapListener> sessionListeners;
synchronized (sessionToListeners) {
if (!sessionToListeners.containsKey(httpSession)) {
sessionToListeners.put(httpSession, new HashSet<ViewScopeViewMapListener>());
}
sessionListeners = sessionToListeners.get(httpSession);
}
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (sessionListeners) {
Set<ViewScopeViewMapListener> toRemove = new HashSet<>();
for (ViewScopeViewMapListener viewMapListener : sessionListeners) {
if (viewMapListener.checkRoot()) {
toRemove.add(viewMapListener);
}
}
sessionListeners.removeAll(toRemove);
sessionListeners.add(listener);
}
if (!FacesContext.getCurrentInstance().getExternalContext().getSessionMap().containsKey("sessionBindingListener")) {
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("sessionBindingListener", this);
}
}
#Override
public Object resolveContextualObject(String key) {
return null;
}
#Override
public void valueBound(HttpSessionBindingEvent event) {
LOGGER.debug("Session event bound {}", event.getName());
}
#Override
public void valueUnbound(HttpSessionBindingEvent event) {
LOGGER.debug("Session event unbound {}", event.getName());
final Set<ViewScopeViewMapListener> listeners;
synchronized (sessionToListeners) {
if (sessionToListeners.containsKey(event.getSession())) {
listeners = sessionToListeners.get(event.getSession());
sessionToListeners.remove(event.getSession());
} else {
listeners = null;
}
}
if (listeners != null) {
for (ViewScopeViewMapListener listener : listeners) {
listener.doCallback();
}
}
}
public void clearFromListener(ViewScopeViewMapListener listener) {
LOGGER.debug("Removing listener from map");
HttpSession httpSession = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);
if (httpSession != null) {
synchronized (sessionToListeners) {
if (sessionToListeners.containsKey(httpSession)) {
sessionToListeners.get(httpSession).remove(listener);
}
}
}
}
}

Resources