I need to create a Conditional Bean in Spring. The use case is as following:
Class 1
In this class we are trying to create the Bean, which should be created for some clients who have the required permission, and for others it will return empty(). Thus the application should boot-up for all the clients without the BeanCreationException
#org.springframework.context.annotation.Configuration
public class SomeBeanConfiguration {
#Bean
public Optional<SomeBean> someBean() {
// whoAmI() ? returns IAmClient_1 - for whom this bean should be created
// whoAmI() ? returns IAmClient_2 - for whom this bean should not be created
final String somePermission = whoAmI();
try {
return Optional.of(SomeBean.builder()
.withPermission(new SomeCredentialsProvider(somePermission))
.build());
} catch (Exception ex) {
LOG.error("SomeBean creation exception : ", ex);
}
return Optional.empty();
}
}
Class 2
Where we are using this Bean in Constructor injection
#Bean
public SomeHelper someHelper(Optional<SomeBean> someBean) {
return new someHelper(someBean);
}
But the someHelper for client, who have permission are also getting an Optional.empty() in constructor.
What I am doing wrong here? Can anyone please help?
You need to change your method that's creating the bean. It should not be returning a bean of type Optional, it should be returning a bean of type SomeBean. Also, consider rewriting your logic to something more understandable, like dropping the catch block and creating the bean based on the output of whoAmI().
#Bean
public SomeBean someBean() {
// whoAmI() ? returns IAmClient_1 - for whom this bean should be created
// whoAmI() ? returns IAmClient_2 - for whom this bean should not be created
String somePermission = whoAmI();
if (somePermission.equals("IAmClient_1") {
return SomeBean.builder().withPermission(newSomeCredentialsProvider(somePermission)).build());
} else {
return null;
}
}
Now, when you autowire the Optional, the optional will contain the bean for IAmClient_1, and will be empty for all other cases.
In my opinion, it would be better to always construct SomeBean and just modify its behavior based on the value of the permission you're checking, but that's up to you.
Related
I have a #Component like below:
#Component
public class FilenetConnection {
#Value("${filenet.url}")
String url;
#Value("${filenet.username}")
String username;
#Value("${filenet.password}")
String password;
#Bean
public Connection getCPEConnection() {
try {
Connection conn = Factory.Connection.getConnection(url);
Subject subject = UserContext.createSubject(conn, username, password, "FileNetP8WSI");
UserContext uc = UserContext.get();
uc.pushSubject(subject);
System.out.println("CE Connection" + conn);
return conn;
} catch (Exception ex) {
ex.printStackTrace();
}
return null;
}
}
And in my RestController this is how I am trying to access the Bean method getCPEConnection() return value:
#Autowired
ConfigurableApplicationContext applicationContext;
public FilenetConnection getBeanOfBaseComponent() {
return applicationContext.getBean(FilenetConnection.class);
}
Now everytime I access the bean method's return value using getBeanOfBaseComponent().getCPEConnection() a new object for conn from the (Singleton) #Bean getCPEConnection() is returned. What I am missing here?
So I understand that you do something like this:
You call getBeanOfBaseComponent() and there you get the Instance of FilenetConnection and on this instance you call getCPEConnection().
If I understood this correct it makes sense that it doesn't work.
Because you don't use the bean you just call a normal method which returns you a new Instance of Connenction.
So I don't use this way of accessing beans myself but I guess you need to use applicationContext.getBean(Connection.class); to be able to use the Connection bean.
Or another and easier solution would be to just inject the Connection into your Controller class.
And normally you define beans in classes annotated with #Configuration and not #Component
Defining bean in a spring-boot application (v 2.3.12) as below:
#MyConditionalProperty(key="name", value = "false")
#Bean
#Profile({"local", "dev"})
public FilterRegistrationBean<Filter> myfilterBean(FilterOne filter){
return registerFilter(filter, Integer.MIN_VALUE);
}
#MyConditionalProperty(key="name", value = "true")
#Bean
public FilterRegistrationBean<Filter> myfilterBean(FilterTwo filter){
return registerFilter(filter, Integer.MIN_VALUE);
}
private FilterRegistrationBean<Filter> registerFilter(Filter filter, int order) {
FilterRegistrationBean<Filter> filterRegistrationBean = new FilterRegistrationBean<>();
filterRegistrationBean.setFilter(filter);
filterRegistrationBean.setOrder(order);
filterRegistrationBean.addUrlPatterns("/v1/*");
return filterRegistrationBean;
}
On local active profile, when the value of the "name" property is "false", myfilterBean is created as expected with dependency on FilterOne. Although, across server restarts, don't see it being created consistently.
What could be the reason for this inconsistency and random behavior? Can it be related to the same method name myfilterBean being used to create the bean with one or the other dependency?
Renaming one of the myfilterBean method works. Renamed it as myFilterBeanOne and appropriate bean is loaded based on the conditional property value.
This is my route class.
#Component
public class MyRoute extends RouteBuilder {
#Value("${spring.enablelog}")
public boolean enablelog;
#Value("${spring.enableroutepolicy}")
public boolean enableroutepolicy;
#Override
public void configure() throws Exception {
CronScheduleRoutePolicy routepolicy = new CronScheduleRoutePolicy();
routepolicy.setRouteStartTime("StartTime");
routepolicy.setRouteStoptTime("StopTime");
routepolicy.setRouteStartTime("StartTime");
from("activemq:queue:inputq")
.setProperty(Enablelog, constant(enablelog))
.choice().when(exchangeProperty(Enablelog).isEqualTo(True))
.bean(MyRoute.class, "setlogProperties('*', 'Request', 'Pending','Received Input message')")
.process(logProcessor).endChoice()
.process(msgProcessor).split().body()
.toD("activemq:queue:waitq")
.choice().when(exchangeProperty(Enablelog).isEqualTo(True))
.bean(MyRoute.class, "setlogProperties('*', 'Response', 'Waiting','Response message waiting to be delivered')")
.process(logProcessor).endChoice()
.end();
if (enableroutepolicy == true) {
from("activemq:queue:waitq").routePolicy(routepolicy).noAutoStartup()
.toD("activemq:queue:outputq")
.choice().when(exchangeProperty(Enablelog).isEqualTo(True))
.bean(MyRoute.class, "setlogProperties('*', 'Response', 'Success','Response message delivered')")
.process(logProcessor).endChoice()
.end();
} else {
from("activemq:queue:waitq").toD("activemq:queue:outputq")
.choice().when(exchangeProperty(Enablelog).isEqualTo(True))
.bean(MyRoute.class, "setlogProperties('*', 'Response', 'Success','Response message delivered')")
.process(logProcessor).endChoice()
.end();
}
}
public void setlogPoperties(Exchange exchange, String msgtype, String status, String statusMessage ) {
exchange.setrPoperty("MessageType", msgtype);
exchange.setrPoperty("Status",status);
exchange.setrPoperty("StatusMessage",statusMessage);
}
}
In this Route I have two routes
From InQueue to WaitQueue
From WaitQueue to OutQueue.
First route is always activated and I am activating the second route according to the requirement. In the second route I am not able to call .bean() and pass the params to the setlogPoperties(). I am new to the camel and not sure how to fix this. Any kind of help is appreciated.
Thanks,
The root cause of this problem might be that 'enablelog' and 'enableroutepolicy' are not getting picked up from your property file and as the default value of boolean is false your code is unable to reach setlogPoperties().
You have annotated the class as a #Component. So in order for #Value to retrieve values from the property file, please make sure you are either defining a #PropertySource annotated in this class itself or you are creating a bean for this component in a separate #Configuration annotated class.
I'm having some trouble with transaction management in EJB3.0. What I want to do is to log an error into the database in case an exception happens.
For that purpose I have 2 stateless beans: Bean A and Bean B.
Bean A does the following:
save something
call Bean B to log an error if needed
In Step 1, the save is basically using the EntityManager#merge(-) method.
In Step 2, I have put the following lines at the top of Bean B:
#Stateless(name = "ErrorLogDAO", mappedName = "ErrorLogDAO")
#Remote
#Local
#TransactionManagement(TransactionManagementType.CONTAINER)
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class ErrorLogDAOBean {...}
However, when an exception is taking place in the save method, I'm catching it and then I manually invoke the ctx.setRollBackOnly() method and after that I call ErrorLogDAOBean that inserts an error log to the DB. But the error log is not being inserted and the error I'm getting is:
javax.transaction.TransactionRolledbackException: EJB Exception: :
weblogic.transaction.internal.AppSetRollbackOnlyException at
weblogic.transaction.internal.TransactionImpl.setRollbackOnly(TransactionImpl.java:551)
at
weblogic.transaction.internal.TransactionManagerImpl.setRollbackOnly(TransactionManagerImpl.java:319)
at
weblogic.transaction.internal.TransactionManagerImpl.setRollbackOnly(TransactionManagerImpl.java:312)
at
org.eclipse.persistence.transaction.JTATransactionController.markTransactionForRollback_impl(JTATransactionController.java:145)
at
org.eclipse.persistence.transaction.AbstractTransactionController.markTransactionForRollback(AbstractTransactionController.java:196)
at
org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.rollbackTransaction(UnitOfWorkImpl.java:4486)
at
org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabase(UnitOfWorkImpl.java:1351)
at
org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.commitToDatabase(RepeatableWriteUnitOfWork.java:468)
at
org.eclipse.persistence.internal.sessions.UnitOfWorkImpl.commitToDatabaseWithPreBuiltChangeSet(UnitOfWorkImpl.java:1439)
at
org.eclipse.persistence.internal.sessions.RepeatableWriteUnitOfWork.writeChanges(RepeatableWriteUnitOfWork.java:316)
at
org.eclipse.persistence.internal.jpa.EntityManagerImpl.flush(EntityManagerImpl.java:527)
....
I'm well familiar with transaction management logic, and based on the code above I assumed I had this covered, but it appears not.
Any ideas?
UPDATE
Bean A Code:
#TransactionManagement(value = TransactionManagementType.CONTAINER)
#TransactionAttribute(value = REQUIRED)
public class WMServiceBOBean {
public void saveInBeanA {
int errorCode = save();
if (errorCode != SUCCESS)
{
ClassX.logError();
ctx.setRollbackOnly();
return errorCode;
}
}
}
Class X Code:
public class classX
{
...
public void logError()
{
ErrorLog e = new ErrorLog;
BeanB beanB = //Local lookup of Bean B
beanB.insertErrorLog (e);
}
...
}
BEAN B Code:
#TransactionManagement(TransactionManagementType.CONTAINER)
#TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class ErrorLogDAOBean
{
...
public void insertErrorLog (ErrorLog e)
{
merge (e);
}
...
}
I finally figured out the problem.
Here's the problem, when looking up the ErrorLogBean, i was instantiating a new Persistence Manager. When the transaction gets flagged for rollback, the process of getting a new PM was failing. I know it doesn't make sense to get a new Persistence Manager but it was part of test we are conducting.
Thanks Piotr for all your help in this!
Is there a way to get all existing session beans managed by Spring at runtime? Getting them for the current user is easy.
Any suggestions?
Thanks,
XLR
I don't do Spring, but in normal JSF/JSP/Servlet you would grab HttpSessionBindingListener for this. Basically you need to give the session scoped bean a static List<Bean> property and implement the interface accordingly that it updates the static list in the valueBound() and valueUnbound() methods.
You can find a detailed code example in this answer.
Here is a solution I came up with that utilizes Spring:
I make a normal Spring singleton bean called SessionBeanHolder.
This bean holds a list of my session beans.
When a user logs in, I add the session bean to my SessionBeanHolder.
When referring to session beans in Spring, you are actually referring to proxies.
So the key thing to making this work was to fetch the underlying bean to add to the SessionBeanHolder.
Below is the sample code:
Note: My session bean is called SessionInfo.
#Scope(value="singleton")
#Component
public class SessionBeanHolder {
static Set<SessionInfo> beans;
public SessionBeanHolder() {
beans = new HashSet<SessionInfo>();
}
public Collection<SessionInfo> getBeans() {
return beans;
}
public void addBean(SessionInfo bean) {
try {
this.beans.add(removeProxyFromBean(bean));
} catch (Exception e) {
e.printStackTrace();
}
}
// Fetch the underlying bean that the proxy refers to
private SessionInfo removeProxyFromBean(SessionInfo proxiedBean) {
if (proxiedBean instanceof Advised) {
try {
return (SessionInfo) ((Advised) proxiedBean).getTargetSource().getTarget();
} catch (Exception e) {
throw new RuntimeException(e);
}
} else {
return proxiedBean;
}
}
}
Naturally, whenever you want to add session bean or fetch a list of all beans, just autowire the SessionBeanHolder and use its methods.
#Autowired
SessionBeanHolder sessionBeanHolder;