How to replace default SortArgumentResolver - spring

I need to add private static final Sort sortById = new Sort(Sort.Direction.DESC, ID); to each Pageable. I guess, the best way to do that is to create decorator/adapter for SortArgumentResolver.
I've created class:
public class IdSortArgumentResolver implements SortArgumentResolver {
private static final String ID = "id";
private static final Sort sortById = new Sort(Sort.Direction.DESC, ID);
private final SortArgumentResolver delegate;
public IdSortArgumentResolverAdapter(SortArgumentResolver delegate) {
this.delegate = delegate;
}
#Override
public boolean supportsParameter(MethodParameter methodParameter) {
return delegate.supportsParameter(methodParameter);
}
#Override
public Sort resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {
Sort sort = delegate.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
if (isNull(sort)) {
return sortById;
}
if (containsSortById(sort)) {
return sort;
}
return sort.and(sortById);
}
private static boolean containsSortById(Sort currentSort) {
return StreamSupport.stream(currentSort.spliterator(), false)
.anyMatch(order -> ID.equalsIgnoreCase(order.getProperty()));
}
}
How can I change default SortArgumentResolver to IdSortArgumentResolver ? Is it possible? or maybe there is a better way to do that...
P.S It's spring-boot 1.5.2 RELEASE and current SortHandlerMethodArgumentResolver is configured in SpringDataWebConfiguration

Note: in your post, your class name is IdSortArgumentResolver but your constructor is IdSortArgumentResolverAdapter.
Create a configuration class extending WebMvcConfigurerAdapter:
#Configuration
public class WebMvcConfiguration extends WebMvcConfigurerAdapter {
// ...
}
Override the addArgumentResolvers method and configure your Sort and Pageable resolvers:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
// FIXME Replace null with what you want
SortArgumentResolver sortResolver = new IdSortArgumentResolver(null);
// For sorting resolution alone
argumentResolver.add(sortResolver);
PageableHandlerMethodArgumentResolver pageableResolver = new PageableHandlerMethodArgumentResolver(sortResolver);
// For sorting resolution encapsulated inside a pageable
argumentResolver.add(pageableResolver);
}
Better solution without a custom class:
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
SortHandlerMethodArgumentResolver sortResolver = new SortHandlerMethodArgumentResolver();
sortResolver.setFallbackSort(new Sort(Sort.Direction.DESC, "id"));
// For sorting resolution alone
argumentResolver.add(sortResolver);
PageableHandlerMethodArgumentResolver pageableResolver = new PageableHandlerMethodArgumentResolver(sortResolver);
// For sorting resolution encapsulated inside a pageable
argumentResolver.add(pageableResolver);
}

Starting in spring-data-commons version 2.0, there is are 2 new classes that will make this kind of thing easier:
SortHandlerMethodArgumentResolverCustomizer
PageableHandlerMethodArgumentResolverCustomizer
Unfortunately that's not the version that ships with the current version of Spring Boot, so replace at your own risk.
#Bean SortHandlerMethodArgumentResolverCustomizer sortCustomizer() {
// s is SortHandlerMethodArgumentResolver
return s -> s.setPropertyDelimiter("<-->");
}
In this case, one would probably call resolveArgument to manipulate it.
Spring Data Web Support

for spring boot 2.2.1.RELEASE:
#Configuration
public class LocaleConfiguration implements WebMvcConfigurer {
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
SortHandlerMethodArgumentResolver sortArgumentResolver = new SortHandlerMethodArgumentResolver();
Sort defaultSort = Sort.by(new Sort.Order(Sort.Direction.DESC, "id"));
sortArgumentResolver.setFallbackSort(defaultSort);
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver(sortArgumentResolver);
resolver.setOneIndexedParameters(true);
resolver.setMaxPageSize(20);
resolvers.add(resolver);
}
}

Related

Spring Cache Caffeine bulk retrieval

Is it possible to use Caffeine's CacheLoader::loadAll with #Cacheable annotated method with collection parameter, like
#Cacheable(cacheNames = "exampleCache", cacheManager="exampleCacheManager", keyGenerator = "complexKeyGenerator")
List<String> getItems(List<String> keys, String commonForEveryKey) {
return ...
}
#Component
class ComplexKeyGenerator implements KeyGenerator {
#Override
public Object generate(Object target, Method method, Object... params) {
return ((List<String>)params[0]).stream()
.map(item -> new ComplexKey(item, (String) params[1]))
.collect(Collectors.toList());
}
}
#Data
#AllArgsConstructor
class ComplexKey {
String key;
String commonGuy;
}
class CustomCacheLoader implements CacheLoader<ComplexKey, String> {
#Override
public #Nullable String load(#NonNull ComplexKey key) throws Exception {
return loadAll(List.of(key)).get(key);
}
#Override
public #NonNull Map<#NonNull ComplexKey, #NonNull String> loadAll(#NonNull Iterable<? extends #NonNull ComplexKey> keys)
throws Exception {
return ...
}
}
#Bean
CacheManager exampleCacheManager(LoadingCache exampleCache) {
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
cacheManager.registerCustomCache("exampleCache", exampleCache());
return cacheManager;
}
#Bean
Cache<Object, Object> exampleCache() {
return Caffeine.newBuilder()
.maximumSize(1000)
.expireAfterWrite(1, TimeUnit.HOURS)
.recordStats()
.build(new CustomCacheLoader());
}
Looks like Spring Cache invokes CustomCacheLoader::load instead of CustomCacheLoader::loadAll and fails on ClassCastException since it cannot cast collection of keys into single key.
What else should I configure to make it work?
Unfortunately Spring doesn't support retrieving of a collection of cached items by a collection of keys via #Cacheable mechanism.
Here's an issue on that: https://github.com/spring-projects/spring-framework/issues/23221
One option to achieve this is to use a custom library ( https://github.com/qaware/collection-cacheable-for-spring) that provides a #CollectionCacheable annotation:
#CollectionCacheable(cacheNames = "myCache")
Map<Long, MyEntity> findByIds(Collection<Long> ids) {
// do efficient batch retrieve of many MyEntity's and build result map
}
If you'd really like to stick with the code you have you could generalize it to something similar:
#Component
class ComplexKeyGenerator implements KeyGenerator {
#Override
public Object generate(Object target, Method method, Object... params) {
if (params.length < 2 || !(params[0] instanceof Collection && params[1] instanceof String)) {
return SimpleKeyGenerator.generateKey(params);
}
return ((Collection<String>) params[0]).stream()
.map(item -> new ComplexKey(item, (String) params[1]))
.collect(Collectors.toList());
}
}
class CustomCacheLoader implements CacheLoader<Object, Object> {
#Override
public Object load(Object key) throws Exception {
final Collection<Object> keys = (key instanceof Collection) ?
((Collection<Object>) key) : Collections.singletonList(key);
final Collection<Object> values = new ArrayList<>(loadAll(keys).values());
return values;
}
#Override
public Map<Object, Object> loadAll(Iterable<?> keys) throws Exception {
...
}
}

Spring Boot v2.2.2.RELEASE throw error when Pagination Size more than anticipated

I'm using Spring Boot (v2.2.2.RELEASE) + Spring Data Mongo + Spring REST + Spring HATEOAS example. In this example, if consumer sends Pagination max-page-size=200 more than 200 then I would need to show the error message saying maximum allowable size is 200.
I went through numerous links like: spring.data.rest.max-page-size does not seem to work?, but none of the solution is working for me yet.
I am using HATEOAS implementation and using PagedResourcesAssembler and RepresentationModelAssemblerSupport.
Any pointers?
#Configuration
public class RespositoryConfiguration extends RepositoryRestMvcConfiguration {
#Value("${paging.default.pageSize}")
private int size;
#Value("${paging.default.page}")
private int page;
public RespositoryConfiguration(ApplicationContext context, ObjectFactory<ConversionService> conversionService) {
super(context, conversionService);
}
#Override
#Bean
public HateoasPageableHandlerMethodArgumentResolver pageableResolver() {
HateoasPageableHandlerMethodArgumentResolver resolver = super.pageableResolver();
resolver.setPageParameterName("page");
resolver.setSizeParameterName("size");
resolver.setOneIndexedParameters(false);
resolver.setFallbackPageable(PageRequest.of(0, 25));
resolver.setMaxPageSize(200);
return resolver;
}
}
Note: I also used spring.data.web.pageable.max-page-size=200 in application.properties file.
I was able to solve this issue with the below code and this works very fine.
#Configuration
public class WebConfig implements WebMvcConfigurer {
...
....
#Override
public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
PageableHandlerMethodArgumentResolver resolver = new PageableHandlerMethodArgumentResolver() {
#Override
public Pageable resolveArgument(MethodParameter methodParameter,
#Nullable ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
#Nullable WebDataBinderFactory binderFactory) {
Pageable p = super.resolveArgument(methodParameter, mavContainer, webRequest, binderFactory);
if(p.getPageSize() > Integer.parseInt(env.getProperty("max.pagination.size"))) {
throw new MandatoryResourceException(env.getProperty("max.pagination.size.error.message"),
env.getProperty("max.pagination.size.error.code"));
}
return p;
}
};
resolvers.add(resolver);
WebMvcConfigurer.super.addArgumentResolvers(resolvers);
}
}

Spring Security: How do I enable custom expression result type support?

In my Spring Boot application I'm using the #PreAuthorize annotation in my controller methods to make them authorized. The expressions use simple boolean-returning methods, like this:
#ResponseStatus(OK)
#PreAuthorize("#auth.authentication.mayReadMe(principal)")
public UserDto readMe() {
...
The mayReadMe(...) method simply returns a boolean value, however it uses ternary logic under the hood and just converts a special enum to boolean:
boolean mayReadMe(#Nonnull UserDetails principal);
Now let's say I want to rework the authorization components and let the method return the enum:
#Nonnull
foo.bar.FooBarEnum mayReadMe(#Nonnull final UserDetails principal);
However, I'm getting the following exception:
java.lang.IllegalArgumentException: Failed to evaluate expression '#primaryAuth.authentication.mayReadMe(principal)'
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:15)
at org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice.before(ExpressionBasedPreInvocationAdvice.java:44)
at org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter.vote(PreInvocationAuthorizationAdviceVoter.java:57)
at org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter.vote(PreInvocationAuthorizationAdviceVoter.java:25)
at org.springframework.security.access.vote.AffirmativeBased.decide(AffirmativeBased.java:62)
at org.springframework.security.access.intercept.AbstractSecurityInterceptor.beforeInvocation(AbstractSecurityInterceptor.java:232)
at org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor.invoke(MethodSecurityInterceptor.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:655)
...
Caused by: org.springframework.expression.spel.SpelEvaluationException: EL1001E:(pos 0): Type conversion problem, cannot convert from #javax.annotation.Nonnull foo.bar.FooBarEnum to java.lang.Boolean
at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:78)
at org.springframework.expression.common.ExpressionUtils.convertTypedValue(ExpressionUtils.java:53)
at org.springframework.expression.spel.standard.SpelExpression.getValue(SpelExpression.java:301)
at org.springframework.security.access.expression.ExpressionUtils.evaluateAsBoolean(ExpressionUtils.java:11)
... 113 common frames omitted
Caused by: org.springframework.core.convert.ConverterNotFoundException: No converter found capable of converting from type [#javax.annotation.Nonnull foo.bar.FooBarEnum] to type [java.lang.Boolean]
at org.springframework.core.convert.support.GenericConversionService.handleConverterNotFound(GenericConversionService.java:313)
at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:195)
at org.springframework.expression.spel.support.StandardTypeConverter.convertValue(StandardTypeConverter.java:74)
... 116 common frames omitted
The exception message is really clear, but I can't inject my custom converter in any way. What I've tried so far:
Registering custom converters via WebMvcConfigurerAdapter.addFormatters(FormatterRegistry) (both Converter and GenericConverter)
Bean-ining a custom ExpressionBasedPreInvocationAdvice (but it shouldn't work as far as I understand)
... and a few other ways I can't recall after spending a few hours unfortunately.
How do I inject a custom type converter so the #PreAuthorization expressions could be aware of the foo.bar.FooBarEnum as the returning type?
Edit 1
Why do I need a custom type to be returned, and not a boolean. I'm also writing a simple REST API self-describing subsystem, just a simple GET /api endpoint to return a list of endpoints and so on. This list consists of a certain objects describing API end point, HTTP method, incoming and outgoing DTOs, and the last thing I'm trying to add to the definition object is an endpoint authorization policy expression. Note that it's not a good idea to return the #PreAuthorize string expression (I mean a raw string), but it might be good to return a custom object describing the authorization rules. What I want the most is returning an object like:
public final class AuthorizationExpression
implements BooleanSupplier {
...
public IExpression toExpression() {
...
}
where BooleanSupplier is expected to be used in the converter I'm trying to inject in order to satisfy the authorization needs -- just return true or false, and where IExpression is expected to be toString-ed in the GET /api handler using the Spring expression evaluator. Hence the mayReadMe signature might be as follows:
AuthorizationExpression mayReadMe(...)
so I could use AuthorizationExpression up to a certain use case. The FooBarEnum is just a simplification for the original question prior to the edit.
A suggestion, let your enum implement the conversion method:
public enum FooBarEnum {
// previous code
public boolean booleanValue() {
// TODO
}
}
And change your annotation:
#PreAuthorize("#auth.authentication.mayReadMe(principal).booleanValue()")
Figured it out. I only need to tune the DefaultMethodSecurityExpressionHandler instance. Let's assume the net two classes as library ones:
public abstract class CustomTypesGlobalMethodSecurityConfiguration
extends GlobalMethodSecurityConfiguration {
protected abstract ApplicationContext applicationContext();
protected abstract ConversionService conversionService();
#Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
final ApplicationContext applicationContext = applicationContext();
final TypeConverter typeConverter = new StandardTypeConverter(conversionService());
final DefaultMethodSecurityExpressionHandler handler = new DefaultMethodSecurityExpressionHandler() {
#Override
public StandardEvaluationContext createEvaluationContextInternal(final Authentication authentication, final MethodInvocation methodInvocation) {
final StandardEvaluationContext decoratedStandardEvaluationContext = super.createEvaluationContextInternal(authentication, methodInvocation);
return new ForwardingStandardEvaluationContext() {
#Override
protected StandardEvaluationContext standardEvaluationContext() {
return decoratedStandardEvaluationContext;
}
#Override
public TypeConverter getTypeConverter() {
return typeConverter;
}
};
}
};
handler.setApplicationContext(applicationContext);
return handler;
}
}
where ForwardingStandardEvaluationContext is a simple forwarding decorator to decorate an instance of StandardEvaluationContext because the latter is ConversionService-aware:
public abstract class ForwardingStandardEvaluationContext
extends StandardEvaluationContext {
protected abstract StandardEvaluationContext standardEvaluationContext();
#Override public void setRootObject(final Object rootObject, final TypeDescriptor typeDescriptor) { standardEvaluationContext().setRootObject(rootObject, typeDescriptor); }
#Override public void setRootObject(final Object rootObject) { standardEvaluationContext().setRootObject(rootObject); }
#Override public TypedValue getRootObject() { return standardEvaluationContext().getRootObject(); }
#Override public void addConstructorResolver(final ConstructorResolver resolver) { standardEvaluationContext().addConstructorResolver(resolver); }
#Override public boolean removeConstructorResolver(final ConstructorResolver resolver) { return standardEvaluationContext().removeConstructorResolver(resolver); }
#Override public void setConstructorResolvers(final List<ConstructorResolver> constructorResolvers) { standardEvaluationContext().setConstructorResolvers(constructorResolvers); }
#Override public List<ConstructorResolver> getConstructorResolvers() { return standardEvaluationContext().getConstructorResolvers(); }
#Override public void addMethodResolver(final MethodResolver resolver) { standardEvaluationContext().addMethodResolver(resolver); }
#Override public boolean removeMethodResolver(final MethodResolver methodResolver) { return standardEvaluationContext().removeMethodResolver(methodResolver); }
#Override public void setMethodResolvers(final List<MethodResolver> methodResolvers) { standardEvaluationContext().setMethodResolvers(methodResolvers); }
#Override public List<MethodResolver> getMethodResolvers() { return standardEvaluationContext().getMethodResolvers(); }
#Override public void setBeanResolver(final BeanResolver beanResolver) { standardEvaluationContext().setBeanResolver(beanResolver); }
#Override public BeanResolver getBeanResolver() { return standardEvaluationContext().getBeanResolver(); }
#Override public void addPropertyAccessor(final PropertyAccessor accessor) { standardEvaluationContext().addPropertyAccessor(accessor); }
#Override public boolean removePropertyAccessor(final PropertyAccessor accessor) { return standardEvaluationContext().removePropertyAccessor(accessor); }
#Override public void setPropertyAccessors(final List<PropertyAccessor> propertyAccessors) { standardEvaluationContext().setPropertyAccessors(propertyAccessors); }
#Override public List<PropertyAccessor> getPropertyAccessors() { return standardEvaluationContext().getPropertyAccessors(); }
#Override public void setTypeLocator(final TypeLocator typeLocator) { standardEvaluationContext().setTypeLocator(typeLocator); }
#Override public TypeLocator getTypeLocator() { return standardEvaluationContext().getTypeLocator(); }
#Override public void setTypeConverter(final TypeConverter typeConverter) { standardEvaluationContext().setTypeConverter(typeConverter); }
#Override public TypeConverter getTypeConverter() { return standardEvaluationContext().getTypeConverter(); }
#Override public void setTypeComparator(final TypeComparator typeComparator) { standardEvaluationContext().setTypeComparator(typeComparator); }
#Override public TypeComparator getTypeComparator() { return standardEvaluationContext().getTypeComparator(); }
#Override public void setOperatorOverloader(final OperatorOverloader operatorOverloader) { standardEvaluationContext().setOperatorOverloader(operatorOverloader); }
#Override public OperatorOverloader getOperatorOverloader() { return standardEvaluationContext().getOperatorOverloader(); }
#Override public void setVariable(final String name, final Object value) { standardEvaluationContext().setVariable(name, value); }
#Override public void setVariables(final Map<String, Object> variables) { standardEvaluationContext().setVariables(variables); }
#Override public void registerFunction(final String name, final Method method) { standardEvaluationContext().registerFunction(name, method); }
#Override public Object lookupVariable(final String name) { return standardEvaluationContext().lookupVariable(name); }
#Override public void registerMethodFilter(final Class<?> type, final MethodFilter filter) throws IllegalStateException { standardEvaluationContext().registerMethodFilter(type, filter); }
}
And then a couple application classes:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, jsr250Enabled = false)
class SecurityConfiguration
extends CustomTypesGlobalMethodSecurityConfiguration {
private final ApplicationContext applicationContext;
private final ConversionService conversionService;
public SecurityConfiguration(
#Autowired final ApplicationContext applicationContext,
#Autowired final ConversionService conversionService
) {
this.applicationContext = applicationContext;
this.conversionService = conversionService;
}
#Override
protected ApplicationContext applicationContext() {
return applicationContext;
}
#Override
protected ConversionService conversionService() {
return conversionService;
}
}
And finally the conversion service configuration:
#Configuration
class ConversionConfiguration {
#Bean
public ConversionService conversionService() {
final DefaultConversionService conversionService = new DefaultConversionService();
conversionService.addConverter(FooBar.class, Boolean.class, FooBar::mayProceed);
return conversionService;
}
}
The code above makes #PreAuthorize to understand FooBar-returning expressions.

Spring Boot Custom #Async Wrap Callable

I am working on an application that supports multi tenancy. The tenant's unqiue identifier is stored in a thread local and can be accessed via some service.
To allow parallel processing, I have created a Callable wrapper, sets the thread local variable:
class TenantAwareCallable<T> implements Callable<T> {
private final String tenantName;
private final Callable<T> delegate;
TenantAwareCallable(Callable<T> delegate, String tenantName) {
this.delegate = delegate;
this.tenantName = tenantName;
}
#Override
public T call() throws Exception {
// set threadlocal
TenantContext.setCurrentTenantName(tenantName);
try {
return delegate.call();
} catch (Exception e) {
// log and handle
} finally {
TenantContext.clear();
}
}
}
This can already be used in the application. But what I would like to have is some custom #Async annotation, like for example #TenantAwareAsync or #TenantPreservingAsync, that wraps the callable, created by Spring in this one and then executes it.
Is there some way to get started with this?
Thanks in advance!
I have a working solution for this, so I think sharing it here might help some people some day.
I solved it not by using the TenantAwareCallable but by customizing the Executor service. I chose to extend Spring's ThreadPoolTaskScheduler (since this is what we use in the project).
public class ContextAwareThreadPoolTaskScheduler extends ThreadPoolTaskScheduler {
#Override
protected ScheduledExecutorService createExecutor(int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
return new ContextAwareThreadPoolTaskExecutor(poolSize, threadFactory, rejectedExecutionHandler);
}
}
The actual setting of the context data is done in a customized ScheduledThreadPoolExecutor:
public class ContextAwareThreadPoolTaskExecutor extends ScheduledThreadPoolExecutor {
public ContextAwareThreadPoolTaskExecutor(int poolSize, ThreadFactory threadFactory, RejectedExecutionHandler rejectedExecutionHandler) {
super(poolSize, threadFactory, rejectedExecutionHandler);
}
#Override
protected <V> RunnableScheduledFuture<V> decorateTask(Callable<V> callable, RunnableScheduledFuture<V> task) {
return new ContextAwareTask<V>(task);
}
#Override
protected <V> RunnableScheduledFuture<V> decorateTask(Runnable runnable, RunnableScheduledFuture<V> task) {
return new ContextAwareTask<V>(task);
}
static private class ContextAwareTask<T> implements RunnableScheduledFuture<T> {
private final RunnableScheduledFuture<T> delegate;
private final TenantContextHolder multitenantContextHolder;
private final LoggingContextHolder loggingContextHolder;
private final SecurityContext securityContext;
ContextAwareTask(RunnableScheduledFuture<T> delegate) {
this.delegate = delegate;
multitenantContextHolder = TenantContextHolder.newInstance();
loggingContextHolder = LoggingContextHolder.newInstance();
securityContext = SecurityContextHolder.getContext();
}
#Override
public void run() {
multitenantContextHolder.apply();
loggingContextHolder.apply();
SecurityContextHolder.setContext(securityContext);
delegate.run();
SecurityContextHolder.clearContext();
loggingContextHolder.clear();
multitenantContextHolder.clear();
}
// all other methods are just delegates
}
}
The Holders are basically just objects to store context state and apply it in the new thread.
public class TenantContextHolder {
private String tenantName;
public static TenantContextHolder newInstance() {
return new TenantContextHolder();
}
private TenantContextHolder() {
this.tenantName = TenantContext.getCurrentTenantName();
}
public void apply() {
TenantContext.setCurrentTenantName(tenantName);
}
public void clear() {
TenantContext.clear();
}
}
The custom implementation of the Scheduler can then be configured in the Spring environment.
#Configuration
public class AsyncConfiguration implements AsyncConfigurer {
private ThreadPoolTaskScheduler taskScheduler;
#Bean
public ThreadPoolTaskScheduler taskScheduler() {
if (taskScheduler == null) {
taskScheduler = new ContextAwareThreadPoolTaskScheduler();
}
return taskScheduler;
}
#Override
public Executor getAsyncExecutor() {
return taskScheduler();
}
}

Spring MVC with Atmosphere

I have recently started with Atmosphere. I need it to implement it in a Spring MVC application.
Till now I've managed to integrate it with Spring MVC.
I just need to perform a very simple task. I have a counter an instance variable as soon as it reaches 10, a response should be broadcasted to the UI.
Can anyone help me how do I write the code for that in the controller.
I've got the Atmosphere resource into the controller.
AtmosphereArgumentResolver.java
public class AtmosphereArgumentResolver implements HandlerMethodArgumentResolver {
//#Override
public boolean supportsParameter(MethodParameter parameter) {
return AtmosphereResource.class.isAssignableFrom(parameter.getParameterType());
}
//#Override
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception
{
HttpServletRequest httpServletRequest= webRequest.getNativeRequest(HttpServletRequest.class);
return Meteor.build(httpServletRequest).getAtmosphereResource();
}
}
HomeController.java
#Controller
public class HomeController {
private int counter = 0;
private final BroadcasterFactory bf;
public BroadcasterFactory broadcasterFactory()
{
return BroadcasterFactory.getDefault();
}
for(int i=0; i<=15; i++)
{
counter ++;
}
// As soon as the counter reaches 10 I need to send a broadcast message to the UI.
}
Can anyone please help? A skeleton code would also help as in which Atmosphere method to use for this?
I will copy/past the code i use in my application :
Controller :
#ManagedService(path = "/websocket/*")
#Singleton
public class LanesWebSocket {
private final Logger logger = LoggerFactory.getLogger(LanesWebSocket.class);
// private ScheduledExecutorService scheduledExecutorService;
private Future<?> scheduleFixedBroadcast;
private final ObjectMapper mapper = new ObjectMapper();
private SupervisionCenterService supervisionCenterService;
#Ready
public void onReady(final AtmosphereResource resource) {
if (this.supervisionCenterService == null)
supervisionCenterService = SpringApplicationContext.getBean(SupervisionCenterService.class);
Broadcaster bc = BroadcasterFactory.getDefault().lookup("lanes",true);
bc.addAtmosphereResource(resource);
scheduleFixedBroadcast = bc.scheduleFixedBroadcast(new Callable<String>() {
#Override
public String call() throws Exception {
try {
return mapper.writeValueAsString(supervisionCenterService.findCenterData());
} catch (Exception e) {
scheduleFixedBroadcast.cancel(true);
e.printStackTrace();
return null;
}
}
}, 1, TimeUnit.SECONDS);
}
And you also need to register the atmosphere servlet :
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
[...]
#Override
protected void registerDispatcherServlet(ServletContext servletContext) {
super.registerDispatcherServlet(servletContext);
initAtmosphereServlet(servletContext);
}
private void initAtmosphereServlet(ServletContext servletContext) {
AtmosphereServlet servlet = new AtmosphereServlet();
Field frameworkField = ReflectionUtils.findField(AtmosphereServlet.class, "framework");
ReflectionUtils.makeAccessible(frameworkField);
ReflectionUtils.setField(frameworkField, servlet, new NoAnalyticsAtmosphereFramework());
ServletRegistration.Dynamic atmosphereServlet =
servletContext.addServlet("atmosphereServlet", servlet);
atmosphereServlet.setInitParameter("org.atmosphere.cpr.packages", "com.myclient.theproduct.supervision.websocket");
atmosphereServlet.setInitParameter("org.atmosphere.cpr.broadcasterCacheClass", UUIDBroadcasterCache.class.getName());
atmosphereServlet.setInitParameter("org.atmosphere.cpr.broadcaster.shareableThreadPool", "true");
atmosphereServlet.setInitParameter("org.atmosphere.cpr.broadcaster.maxProcessingThreads", "10");
atmosphereServlet.setInitParameter("org.atmosphere.cpr.broadcaster.maxAsyncWriteThreads", "10");
servletContext.addListener(new org.atmosphere.cpr.SessionSupport());
atmosphereServlet.addMapping("/websocket/*");
atmosphereServlet.setLoadOnStartup(3);
atmosphereServlet.setAsyncSupported(true);
}
public class NoAnalyticsAtmosphereFramework extends AtmosphereFramework {
public NoAnalyticsAtmosphereFramework() {
super();
}
#Override
protected void analytics() {
// nothing
}
}
}
Don't ask me the reason of the NoAnalyticsAtmosphereFramework class, it could not work without.
Hope this will help you !

Resources