Spring Boot custom annotation design not working - spring-boot

I am following a tutorial by PacktPublishing where some annotations are used in an example,
but the code is from 2018 and there have probably been some changes.
Spring does not recognize the Annotation when creating a bean.
Specifically, here is an annotation design that just does not work for me locally:
link
Some important code snippets are:
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
#Component
public #interface ChannelHandler {
/**
* Channel patter, alias of value()
*/
String pattern() default "";
/**
* The channel pattern that the handler will be mapped to by {#link WebSocketRequestDispatcher}
* using Spring's {#link org.springframework.util.AntPathMatcher}
*/
String value() default "";
}
#ChannelHandler("/board/*")
public class BoardChannelHandler {
private static final Logger log = LoggerFactory.getLogger(BoardChannelHandler.class);
#Action("subscribe")
public void subscribe(RealTimeSession session, #ChannelValue String channel) {
log.debug("RealTimeSession[{}] Subscribe to channel `{}`", session.id(), channel);
SubscriptionHub.subscribe(session, channel);
}
#Action("unsubscribe")
public void unsubscribe(RealTimeSession session, #ChannelValue String channel) {
log.debug("RealTimeSession[{}] Unsubscribe from channel `{}`", session.id(), channel);
SubscriptionHub.unsubscribe(session, channel);
}
}
#Target({ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface Action {
/**
* The action pattern. It needs to be an exact match.
* <p>For example, "subscribe"
*/
String value() default "";
}
Can you see what the issue is here? Is there some other annotation missing for newer versions
of Spring?
UPDATE - adding other necessary code.
public class ChannelHandlerInvoker {
private static final Logger log = LoggerFactory.getLogger(ChannelHandlerInvoker.class);
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
private String channelPattern;
private Object handler;
// Key is the action, value is the method to handle that action
private final Map<String, Method> actionMethods = new HashMap<>();
public ChannelHandlerInvoker(Object handler) {
Assert.notNull(handler, "Parameter `handler` must not be null");
Class<?> handlerClass = handler.getClass();
ChannelHandler handlerAnnotation = handlerClass.getAnnotation(ChannelHandler.class);
Assert.notNull(handlerAnnotation, "Parameter `handler` must have annotation #ChannelHandler");
Method[] methods = handlerClass.getMethods();
for (Method method : methods) {
Action actionAnnotation = method.getAnnotation(Action.class);
if (actionAnnotation == null) {
continue;
}
String action = actionAnnotation.value();
actionMethods.put(action, method);
log.debug("Mapped action `{}` in channel handler `{}#{}`", action, handlerClass.getName(), method);
}
this.channelPattern = ChannelHandlers.getPattern(handlerAnnotation);
this.handler = handler;
}
public boolean supports(String action) {
return actionMethods.containsKey(action);
}
public void handle(IncomingMessage incomingMessage, RealTimeSession session) {
Assert.isTrue(antPathMatcher.match(channelPattern, incomingMessage.getChannel()), "Channel of the handler must match");
Method actionMethod = actionMethods.get(incomingMessage.getAction());
Assert.notNull(actionMethod, "Action method for `" + incomingMessage.getAction() + "` must exist");
// Find all required parameters
Class<?>[] parameterTypes = actionMethod.getParameterTypes();
// All the annotations for each parameter
Annotation[][] allParameterAnnotations = actionMethod.getParameterAnnotations();
// The arguments that will be passed to the action method
Object[] args = new Object[parameterTypes.length];
try {
// Populate arguments
for (int i = 0; i < parameterTypes.length; i++) {
Class<?> parameterType = parameterTypes[i];
Annotation[] parameterAnnotations = allParameterAnnotations[i];
// No annotation applied on this parameter
if (parameterAnnotations.length == 0) {
if (parameterType.isInstance(session)) {
args[i] = session;
} else {
args[i] = null;
}
continue;
}
// Only use the first annotation applied on the parameter
Annotation parameterAnnotation = parameterAnnotations[0];
if (parameterAnnotation instanceof Payload) {
Object arg = JsonUtils.toObject(incomingMessage.getPayload(), parameterType);
if (arg == null) {
throw new IllegalArgumentException("Unable to instantiate parameter of type `" +
parameterType.getName() + "`.");
}
args[i] = arg;
} else if (parameterAnnotation instanceof ChannelValue) {
args[i] = incomingMessage.getChannel();
}
}
actionMethod.invoke(handler, args);
} catch (Exception e) {
String error = "Failed to invoker action method `" + incomingMessage.getAction() +
"` at channel `" + incomingMessage.getChannel() + "` ";
log.error(error, e);
session.error(error);
}
}
}
#Component
public class ChannelHandlerResolver {
private static final Logger log = LoggerFactory.getLogger(ChannelHandlerResolver.class);
private static final AntPathMatcher antPathMatcher = new AntPathMatcher();
// The key is the channel ant-like path pattern, value is the corresponding invoker
private final Map<String, ChannelHandlerInvoker> invokers = new HashMap<>();
private ApplicationContext applicationContext;
public ChannelHandlerResolver(ApplicationContext applicationContext) {
this.applicationContext = applicationContext;
this.bootstrap();
}
public ChannelHandlerInvoker findInvoker(IncomingMessage incomingMessage) {
ChannelHandlerInvoker invoker = null;
Set<String> pathPatterns = invokers.keySet();
for (String pathPattern : pathPatterns) {
if (antPathMatcher.match(pathPattern, incomingMessage.getChannel())) {
invoker = invokers.get(pathPattern);
}
}
if (invoker == null) {
return null;
}
return invoker.supports(incomingMessage.getAction()) ? invoker : null;
}
private void bootstrap() {
log.info("Bootstrapping channel handler resolver");
Map<String, Object> handlers = applicationContext.getBeansWithAnnotation(ChannelHandler.class);
for (String handlerName : handlers.keySet()) {
Object handler = handlers.get(handlerName);
Class<?> handlerClass = handler.getClass();
ChannelHandler handlerAnnotation = handlerClass.getAnnotation(ChannelHandler.class);
String channelPattern = ChannelHandlers.getPattern(handlerAnnotation);
if (invokers.containsKey(channelPattern)) {
throw new IllegalStateException("Duplicated handlers found for chanel pattern `" + channelPattern + "`.");
}
invokers.put(channelPattern, new ChannelHandlerInvoker(handler));
log.debug("Mapped channel `{}` to channel handler `{}`", channelPattern, handlerClass.getName());
}
}
}
<!-- begin snippet: js hide: false console: true babel: false -->
UPDATE 2
I have managed to make ChannelHandler and Action annotations work by adding #Inherited annotation and using AnnotationUtils.findAnnotation() which traverses its super methods if the annotation is not directly present on the given method itself.
However, I haven't managed to access custom annotation value of type parameter (ChannelValue)
Here, Annotation[][] allParameterAnnotations = actionMethod.getParameterAnnotations();
returns null value.
UPDATE 3 -> SOLVED
Just add #Aspect annotation to your ChannelHandler implementation (e.g.
"BoardChannelHandler").

Looks like bootstrap() method, that goes through all the #ChannelHandler annotated beans is executed too early - try to debug it to check if it detects any beans at this stage.
If not try calling bootstrap() after Spring context is ready (for example listen for ContextRefreshedEvent.

Related

Loading value from json upon start up application

I want to load the values from json file upon the Spring Boot Application is started.
My code for the Configuration File is like the below:
#Configuration
#Getter
public class FedexAPIConfig {
private final static String JSON_FILE = "/static/config/fedex-api-credentials.json";
private final boolean IS_PRODUCTION = false;
private FedexAPICred apiCredentials;
public FedexAPIConfig() {
try (InputStream in = getClass().getResourceAsStream(JSON_FILE);
BufferedReader reader = new BufferedReader(new InputStreamReader(in, StandardCharsets.UTF_8))) {
JSONObject json = new JSONObject();
// this.apiCredentials = new JSONObject(new JSONTokener(reader));
if (IS_PRODUCTION) {
json = new JSONObject(new JSONTokener(reader)).getJSONObject("production");
} else {
json = new JSONObject(new JSONTokener(reader)).getJSONObject("test");
}
System.out.println(json.toString());
this.apiCredentials = FedexAPICred.builder()
.url(json.optString("url"))
.apiKey(json.optString("api_key"))
.secretKey(json.optString("secret_key"))
.build();
} catch (FileNotFoundException fnfe) {
fnfe.printStackTrace();
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
and with this, when the application is in progress of startup, values are successfully printed on the console.Startup console log
When I tried to call this value from other ordinary class, like the below:, it brings nothing but just throws NullPointerException... What are my faults and what shall I do?
public class FedexOAuthTokenManager extends OAuthToken {
private static final String VALIDATE_TOKEN_URL = "/oauth/token";
private static final String GRANT_TYPE_CLIENT = "client_credentials";
private static final String GRANT_TYPE_CSP = "csp_credentials";
#Autowired
private FedexAPIConfig fedexApiConfig;
#Autowired
private Token token;
#Override
public void validateToken() {
// This is the part where "fedexApiConfig" is null.
FedexAPICred fedexApiCred = fedexApiConfig.getApiCredentials();
Response response = null;
try {
RequestBody body = new FormBody.Builder()
.add("grant_type", GRANT_TYPE_CLIENT)
.add("client_id", fedexApiCred.getApiKey())
.add("client_secret", fedexApiCred.getSecretKey())
.build();
response = new HttpClient().post(fedexApiCred.getUrl() + VALIDATE_TOKEN_URL, body);
if (response.code() == 200) {
JSONObject json = new JSONObject(response.body().string());
token.setAccessToken(json.optString("access_token"));
token.setTokenType(json.optString("token_type"));
token.setExpiredIn(json.optInt("expires_in"));
token.setExpiredDateTime(LocalDateTime.now().plusSeconds(json.optInt("expires_in")));
token.setScope(json.optString("scope"));
}
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
}
fedexApiConfg is null even though I autowired it in prior to call.
And this FedexOAuthTokenManager is called from other #Component class by new FedexOAuthTokenManager()
Did you try like below?
Step 1: Create one Configuration class like below
public class DemoConfig implements ApplicationListener<ApplicationPreparedEvent> {
#Override
public void onApplicationEvent(ApplicationPreparedEvent event) {
//Load the values from the JSON file and populate the application
//properties dynamically
ConfigurableEnvironment environment = event.getApplicationContext().getEnvironment();
Properties props = new Properties();
props.put("spring.datasource.url", "<my value>");
//Add more properties
environment.getPropertySources().addFirst(new PropertiesPropertySource("myProps", props));
}
To listen to a context event, a bean should implement the ApplicationListener interface which has just one method onApplicationEvent().The ApplicationPreparedEvent is invoked very early in the lifecycle of the application
Step 2: Customize in src/main/resources/META-INF/spring.factories
org.springframework.context.ApplicationListener=com.example.demo.DemoConfig
Step 3: #Value in spring boot is commonly used to inject the configuration values into the spring boot application. Access the properties as per your wish.
#Value("${spring.datasource.url}")
private String valueFromJSon;
Try this sample first in your local machine and then modify your changes accordingly.
Refer - https://www.baeldung.com/spring-value-annotation
Refer - https://www.knowledgefactory.net/2021/02/aws-secret-manager-service-as.html

Usage of exceptionExpression in Spring Retry

According to documentation, I can use something like this in exceptionExpression: #Retryable(exceptionExpression="message.contains('this can be retried')")
But I want to get response body and check message inside it (from RestClientResponseException), something similar to this: exceptionExpression = "getResponseBodyAsString().contains('important message')"
I tried like that but it doesn't work. So, is it possible to do something similar and check info from responseBody?
Edit: Adding whole #Retryable annotation parameters with Gary Russell's suggestion:
#Retryable(value = HttpClientErrorException.class, exceptionExpression = "#{#root instanceof T(org.springframework.web.client.HttpClientErrorException) AND responseBodyAsString.contains('important message')}")
I'm using actual RestClientResponseException subclass that I'm catching but is still not triggering retry.
With the current release, the expression incorrectly requires static template markers; they will not be needed in 1.3.
#Retryable(exceptionExpression = "#{responseBodyAsString.contains('foo')}")
However, you can't use this expression if there are include or exclude properties so the expression should check the type:
#Retryable(exceptionExpression =
"#{#root instanceof T(org.springframework.web.client.RestClientResponseException) "
+ "AND responseBodyAsString.contains('foo')}")
EDIT
#SpringBootApplication
#EnableRetry
public class So61488237Application {
public static void main(String[] args) {
SpringApplication.run(So61488237Application.class, args).close();
}
#Bean
public ApplicationRunner runner(Foo foo) {
return args -> {
try {
foo.test(1, "foo.");
}
catch (Exception e) {
}
};
}
}
#Component
class Foo {
#Retryable(exceptionExpression =
"#{#root instanceof T(org.springframework.web.client.RestClientException) "
+ "AND responseBodyAsString.contains('foo')}")
public void test(int val, String str) {
System.out.println(val + ":" + str);
throw new RestClientResponseException("foo", 500, "bar", new HttpHeaders(), "foo".getBytes(),
StandardCharsets.UTF_8);
}
}
1:foo.
1:foo.
1:foo.
I've implemented the following approach, which in my opinion is much more convenient.
#Retryable(value = WebClientException.class,
exceptionExpression = RetryCheckerService.EXPRESSION,
maxAttempts = 5,
backoff = #Backoff(delay = 500))
public List<ResultDto> getSomeResource () {}
Here the RetryCheckerService encapsulates all needed logic.
#Service
public class RetryCheckerService {
public static final String EXPRESSION = "#retryCheckerService.shouldRetry(#root)";
public boolean shouldRetry(WebClientException ex) {
if (ex instanceof WebClientResponseException responseException) {
return responseException.getStatusCode().is5xxServerError()
|| responseException.getStatusCode().equals(HttpStatus.NOT_FOUND);
}
if (ex instanceof WebClientRequestException requestException) {
String message = requestException.getMessage();
if (message == null) {
return false;
}
return message.contains("HttpConnectionOverHTTP");
}
return false;
}
}

Dynamic Routing key on RabbitListener Annotation

I need to create a queue linked to Direct Exchange for every user who has logged in to the application. The basement routing will be 'user_' + userId.
That is, every time I receive a message through the user management queue that a user is logged on. Instantiate a bean with scope 'prototype' that contains a method annotated with RabbitListener to declare its queue. To this bean, I passed the userId to be able to configure the name of the queue and routingKey. But I can not access this instance variable in the Spel expression due to a circular reference error.
Here I put the bean with which declares the queue:
#Component("usersHandler")
#Scope(value = "prototype")
public class UsersHandler {
private static Logger logger = LoggerFactory.getLogger(UsersHandler.class);
private Long userId;
public UsersHandler(Long userId) {
this.userId = userId;
}
public Long getUserId() {
return userId;
}
public void setUserId(Long userId) {
this.userId = userId;
}
#RabbitListener(bindings
= #QueueBinding(
value = #Queue(
value = "#{'queue_'.concat(usersHandler.userId)}",
durable = "false",
autoDelete = "true",
arguments = {
#Argument(
name = "x-message-ttl",
value = "#{rabbitCustomProperties.directExchange.queueArguments['x-message-ttl']}",
type = "java.lang.Integer"
)
,
#Argument(
name = "x-expires",
value = "#{rabbitCustomProperties.directExchange.queueArguments['x-expires']}",
type = "java.lang.Integer"
)
,
#Argument(
name = "x-dead-letter-exchange",
value = "#{rabbitCustomProperties.directExchange.queueArguments['x-dead-letter-exchange']}",
type = "java.lang.String"
)
}
),
exchange = #Exchange(
value = "#{rabbitCustomProperties.directExchange.name}",
type = ExchangeTypes.DIRECT,
durable = "#{rabbitCustomProperties.directExchange.durable}",
autoDelete = "#{rabbitCustomProperties.directExchange.autoDelete}",
arguments = {
#Argument(
name = "alternate-exchange",
value = "#{rabbitCustomProperties.directExchange.arguments['alternate-exchange']}",
type = "java.lang.String"
)
}
),
key = "#{'user_'.concat(usersHandler.userId)}")
)
public void handleMessage(#Payload Notification notification) {
logger.info("Notification Received : " + notification);
}
}
This is the other bean in charge of creating as many UserHandler as users have logged in:
#Component("adminHandler")
public class AdminHandler implements UsersManadgementVisitor {
#Autowired
private ApplicationContext appCtx;
private Map<Long, UsersHandler> handlers = new HashMap<Long, UsersHandler>();
private static Logger logger = LoggerFactory.getLogger(AdminHandler.class);
#RabbitListener(queues="#{rabbitCustomProperties.adminExchange.queues['users'].name}")
public void handleMessage(#Payload UsersManadgementMessage message) {
logger.info("Message -> " + message);
message.getType().accept(this, message.getId());
}
#Override
public void visitUserConnected(Long idUser) {
logger.info("Declare new queue for user: " + idUser );
UsersHandler userHandler = appCtx.getBean(UsersHandler.class, idUser);
handlers.put(idUser, userHandler);
}
#Override
public void visitUserDisconnected(Long idUser) {
logger.info("Remove queue for user: " + idUser );
handlers.remove(idUser);
}
}
My question is this:
How can I make the variable userId available in the evaluation context of the SpEL expressions?
You could use a ThreadLocal and the T operator...
#SpringBootApplication
public class So43717710Application {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(So43717710Application.class, args);
UserHolder.setUser("someUser");
context.getBean(Listener.class);
UserHolder.clearUser();
context.getBean(RabbitTemplate.class).convertAndSend("foo", "user_someUser", "bar");
Thread.sleep(5000);
context.close();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Listener listener() {
return new Listener();
}
public static class Listener {
#RabbitListener(bindings = #QueueBinding(value = #Queue("#{'queue_' + T(com.example.UserHolder).getUser()}"),
exchange = #Exchange(value = "foo"),
key = "#{'user_' + T(com.example.UserHolder).getUser()}"))
public void listen(String in) {
System.out.println(in);
}
}
}
public class UserHolder {
private static final ThreadLocal<String> user = new ThreadLocal<String>();
public static void setUser(String userId) {
user.set(userId);
}
public static String getUser() {
return user.get();
}
public static void clearUser() {
user.remove();
}
}
If the ThreadLocal is in a #Bean you can use a bean reference...
#SpringBootApplication
public class So43717710Application {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = SpringApplication.run(So43717710Application.class, args);
UserHolder.setUser("someUser");
context.getBean(Listener.class);
UserHolder.clearUser();
context.getBean(RabbitTemplate.class).convertAndSend("foo", "user_someUser", "bar");
Thread.sleep(5000);
context.close();
}
#Bean
public UserHolder holder() {
return new UserHolder();
}
#Bean
#Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public Listener listener() {
return new Listener();
}
public static class Listener {
#RabbitListener(bindings = #QueueBinding(value = #Queue("#{'queue_' + #holder.user}"),
exchange = #Exchange(value = "foo"),
key = "#{'user_' + #holder.user}"))
public void listen(String in) {
System.out.println(in);
}
}
}

Activtii/OSGI, TaskListener not added as Activiti service to delegate cache

Hello I have a problem with osgi on servicemix.
It cannot bind or unbind service to delegate cache, when using TaskListener interface. JavaDelegate works fine in osgi with delegate expression.
Could this solution solve the problem or is there something else needed with BlueprintContextELResolver? Because the blueprint with the
service ref="something" interface="org.activiti.engine.delegate.TaskListener"/>
package org.activiti.osgi.blueprint;
/**
* #see org.activiti.spring.ApplicationContextElResolver
*/
public class BlueprintELResolver extends ELResolver {
private Map<String, JavaDelegate> delegateMap = new HashMap<String, JavaDelegate>();
private Map<String, TaskListener> taskListenerMap = new HashMap<String, TaskListener>();
private Map<String, ActivityBehavior> activityBehaviourMap = new HashMap<String, ActivityBehavior>();
public Object getValue(ELContext context, Object base, Object property) {
if (base == null) {
// according to javadoc, can only be a String
String key = (String) property;
LOGGER.info("Show string key: {}", key);
LOGGER.info("Show property: {}", property);
for (String name : delegateMap.keySet()) {
if (name.equalsIgnoreCase(key)) {
LOGGER.info("Show property JavaDelegate: {}", name);
context.setPropertyResolved(true);
return delegateMap.get(name);
}
}
for (String name : taskListenerMap.keySet()) {
if (name.equalsIgnoreCase(key)) {
LOGGER.info("Show property TaskListener: {}", name);
context.setPropertyResolved(true);
return taskListenerMap.get(name);
}
}
for (String name : activityBehaviourMap.keySet()) {
if (name.equalsIgnoreCase(key)) {
context.setPropertyResolved(true);
return activityBehaviourMap.get(name);
}
}
}
return null;
}
#SuppressWarnings("rawtypes")
public void bindService(JavaDelegate delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
delegateMap.put(name, delegate);
LOGGER.info("added Activiti service to delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void unbindService(JavaDelegate delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
if(delegateMap.containsKey(name)) {
delegateMap.remove(name);
}
LOGGER.info("removed Activiti service from delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void bindTaskListenerService(TaskListener delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
taskListenerMap.put(name, delegate);
LOGGER.info("added Activiti service to delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void unbindTaskListenerService(TaskListener delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
if(taskListenerMap.containsKey(name)) {
taskListenerMap.remove(name);
}
LOGGER.info("removed Activiti service from delegate cache {}", name);
}
#SuppressWarnings("rawtypes")
public void bindActivityBehaviourService(ActivityBehavior delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
activityBehaviourMap.put(name, delegate);
LOGGER.info("added Activiti service to activity behaviour cache {}", name);
}
#SuppressWarnings("rawtypes")
public void unbindActivityBehaviourService(ActivityBehavior delegate, Map props) {
String name = (String) props.get("osgi.service.blueprint.compname");
if(activityBehaviourMap.containsKey(name)) {
activityBehaviourMap.remove(name);
}
LOGGER.info("removed Activiti service from activity behaviour cache {}", name);
}
public boolean isReadOnly(ELContext context, Object base, Object property) {
return true;
}
public void setValue(ELContext context, Object base, Object property,
Object value) {
}
public Class<?> getCommonPropertyType(ELContext context, Object arg) {
return Object.class;
}
public Iterator<FeatureDescriptor> getFeatureDescriptors(ELContext context,
Object arg) {
return null;
}
public Class<?> getType(ELContext context, Object arg1, Object arg2) {
return Object.class;
}
}
After this I still get:
Exception while invoking TaskListener: Unknown property used in
expression: ${expression}
I have experienced a similar problem where the El resolver was not being fully initiated.
I resolved the problem by overloading the BlueprintExpressionManager when loading the configuration:
public class BlueprintExpressionManagerReordered extends ExpressionManager
{
#Override
protected ELResolver createElResolver(VariableScope variableScope){
CompositeELResolver elResolver = new CompositeELResolver();
elResolver.add(new VariableScopeElResolver(variableScope));
elResolver.add(new ArrayELResolver());
elResolver.add(new ListELResolver());
elResolver.add(new MapELResolver());
if (_blueprintContextELResolver != null) {
elResolver.add(_blueprintContextELResolver);
}
elResolver.add(_blueprintELResolver);
elResolver.add(new BeanELResolver());
return elResolver;
}
}
It is called during init of the process engine configuration:
public class ProcessEngineFactoryWithELResolverReordered extends ProcessEngineFactoryWithELResolver
{
private BlueprintELResolver _blueprintELResolver;
private BlueprintContextELResolver _blueprintContextELResolver;
public void init() throws Exception
{
super.init();
ProcessEngineConfigurationImpl configImpl = (ProcessEngineConfigurationImpl)getProcessEngineConfiguration();
final BlueprintExpressionManagerReordered mgr = new BlueprintExpressionManagerReordered();
configImpl.setExpressionManager(mgr);
if (configImpl.getActivityBehaviorFactory() instanceof DefaultActivityBehaviorFactory) {
((DefaultActivityBehaviorFactory)configImpl.getActivityBehaviorFactory()).setExpressionManager(mgr);
}
if (configImpl.getListenerFactory() instanceof DefaultListenerFactory) {
((DefaultListenerFactory)configImpl.getListenerFactory()).setExpressionManager(mgr);
}
}
And referenced in my context.xml
<bean id="processEngineFactory"
class="com.bp3.oss.custom.ProcessEngineFactoryWithELResolverReordered"
init-method="init"
destroy-method="destroy">
<property name="processEngineConfiguration"
ref="configuration"/>
<property name="bundle" ref="blueprintBundle"/>
<property name="blueprintELResolver"
ref="blueprintELResolver"/>
<property name="blueprintContextELResolver" ref="blueprintContextELResolver"/>
</bean>
Not sure if this will help, but may give you something to go after.

How to make queryparams mandatory in Java Jersey REST services?

I have a REST API that accepts 3 query params. When the query is called without any one of the query parameters, the API executes and returns the result. How do we make the queryparams mandatory? How can I add validation to check if all the parameters are present? Also, please let me know the best approach.
On a very simple level you could just inject the HttpServletRequest and check yourself:
#GET
public Response example(#Context HttpServletRequest request,
#QueryParam("name") String name) {
if (null == request.getParameter("name")) {
ResponseBuilder builder = Response.status(404);
return builder.build();
}
// Do something with name
}
Or you can implement something more elaborate using AOP. Here's a blog post about further options.
jersey doesn't give a mandatory parameter checking functionality out of the box. however you can do something like implementing your own annotation to achieve it.
Below is the annotation code:
#Target(value = ElementType.METHOD)
#Retention(value = RetentionPolicy.RUNTIME)
public #interface Required {
String[] value();
}
You also need a filter, below is the code:
public class RequiredParamResourceFilterFactory implements ResourceFilterFactory {
#Context
private transient HttpServletRequest servletRequest;
private class RequiredParamFilter implements ResourceFilter, ContainerRequestFilter {
private final String[] requiredParams;
protected List<String> parametersValueMissing;
private RequiredParamFilter(String[] requiredParams) {
this.requiredParams = requiredParams;
}
#Override
public ContainerRequest filter(ContainerRequest containerRequest) {
boolean missingMandatoryParameter = false;
List<String> missingParameters = new ArrayList<String>();
List<String> requiredParametersValueMissing = new ArrayList<String>();
List<String> URLParameters = getURLParameters(containerRequest.getQueryParameters());
List<String> methodRequiredParameters = Arrays.asList(requiredParams);
if (methodRequiredParameters != null) {
for (String methodRequiredParam : methodRequiredParameters) {
if (URLParameters == null) {
missingMandatoryParameter = true; //we will check this flag before returning result set to caller
missingParameters.add(methodRequiredParam);
} else if (!URLParameters.contains(methodRequiredParam)) {
missingMandatoryParameter = true; //we will check this flag before returning result set to caller
missingParameters.add(methodRequiredParam);
//Add to required parameters value missing List, only if the parameter is mandatory and value is not provided
// in the URL
} else if (parametersValueMissing.contains(methodRequiredParam)) {
requiredParametersValueMissing.add(methodRequiredParam);
}
}
if (missingMandatoryParameter && requiredParametersValueMissing.size() > 0) {
throw new YourCustomException("Missing Parameters = " + StringHelper.ArrayToString(missingParameters) +
"\nParameter value missing for " + StringHelper.ArrayToString(requiredParametersValueMissing));
} else if (missingMandatoryParameter) {
throw new YourCustomException("Missing Parameters = " + StringHelper.ArrayToString(missingParameters), MisbarErrorCode.VALIDATION_WRONG_INPUT_ERROR, "Customers");
} else if (requiredParametersValueMissing != null &&
requiredParametersValueMissing.size() > 0) {
throw new YourCustomException("Parameter value missing for " + StringHelper.ArrayToString(requiredParametersValueMissing));
}
}
return containerRequest;
}
#Override
public ContainerRequestFilter getRequestFilter() {
return this;
}
#Override
public ContainerResponseFilter getResponseFilter() {
return null;
}
/**
* To fetch the parameters sent to webservice call, these will be used to find if required parameter
* are present or not
*
* #param queryParams the queryparams sent
* #return all the parameters sent in URL
*/
private List<String> getURLParameters(MultivaluedMap<String,String> queryParams) {
parametersValueMissing = new ArrayList<String>();
List<String> arr = new ArrayList<String>();
for(String key:queryParams.keySet())
{
arr.add(key);
if(queryParams.get(key)==null)
parametersValueMissing.add(key);
}
if(!arr.isEmpty())
return arr;
return null;
}
}
#Override
public List<ResourceFilter> create(AbstractMethod am) {
Required required = am.getAnnotation(Required.class);
if(required!=null)
{
return Collections.<ResourceFilter>singletonList(new RequiredParamFilter(required.value()));
}
return null;
}
}
Below sample shows how to use this annotation, so in below webservice; file_id and count are mandatory parameters:
#GET
#Produces(MediaType.APPLICATION_JSON+";charset=utf-8")
#Cacheable(isCacheable = true)
#Path("posts/clusters")
#Required({"file_id","count"})
#Timed
public Response getClusters(
#QueryParam("file_id") Integer fileId,
#QueryParam("count") Integer count,
#DefaultValue("-1")#QueryParam("start_time") Long startTime){
;
}
If mandatory parameters are not provided in webservice call, you receive an error like below, mentioning the parameter names that are missing:
{
message: "Missing Parameters = file_id, count",
errorCode: "600"
}
Hope this solves your problem.

Resources