Spring Framework: Why the active profiles declared programmatically are working if the "refresh" method was not executed? - spring

About Spring Framework 3.x and 4.x (nothing about Spring Boot ) if my memory does not fail me, I remember when the active profiles are declared programmatically then the refresh method must be called, it after of the profiles declaration, otherwise the profiles are not reflected when Spring is running its own Application Context.
It is reflected at:
How to set the active profile when creating Spring context programmatically?
Code from the shared answer
final AnnotationConfigApplicationContext appContext = new AnnotationConfigApplicationContext();
appContext.getEnvironment().setActiveProfiles( "myProfile" );
appContext.register( com.initech.ReleaserConfig.class );
appContext.refresh(); <--- must be executed after of the profiles declaration
Now with Spring Framework 6 for academic purposes I tried the following:
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext("com.manuel.jordan.config");
ctx.getEnvironment().setDefaultProfiles("default", "dev");
ctx.getEnvironment().setActiveProfiles("jdbc", "mysql");
As you can see the refresh method was not executed, it to demonstrate that the active profiles were ignored. But it works, through the following code it was confirmed
for(String activeProfile : ctx.getEnvironment().getActiveProfiles()) {
System.out.println(" " + activeProfile);
}
and even more, the constructor code with the String parameter of the AnnotationConfigApplicationContext is:
/**
* Create a new AnnotationConfigApplicationContext, scanning for components
* in the given packages, registering bean definitions for those components,
* and automatically refreshing the context.
* #param basePackages the packages to scan for component classes
*/
public AnnotationConfigApplicationContext(String... basePackages) {
this();
scan(basePackages);
refresh(); <--------------
}
As you can see the refresh method is executed but remember that this constructor execution happens before of the setActiveProfiles execution.
Therefore
Question
Why the active profiles declared programmatically are working if the refresh method was not executed?
Even more, like a simple plus, if is executed as:
ConfigurableApplicationContext ctx =
new AnnotationConfigApplicationContext("com.manuel.jordan.config");
ctx.getEnvironment().setDefaultProfiles("default", "dev");
ctx.getEnvironment().setActiveProfiles("jdbc", "mysql");
ctx.refresh(); <---
Arises
java.lang.IllegalStateException: GenericApplicationContext does not support multiple refresh attempts: just call 'refresh' once
at org.springframework.context.support.GenericApplicationContext.refreshBeanFactory (GenericApplicationContext.java:293)
at org.springframework.context.support.AbstractApplicationContext.obtainFreshBeanFactory (AbstractApplicationContext.java:672)
at org.springframework.context.support.AbstractApplicationContext.refresh (AbstractApplicationContext.java:554)
at com.manuel.jordan.main.Main.main (Main.java:28)
at org.codehaus.mojo.exec.ExecJavaMojo$1.run (ExecJavaMojo.java:279)
at java.lang.Thread.run (Thread.java:833)

Related

Spring Framework: setDefaultProfiles method does not work as expected as the "-Dspring.profiles.default" property

For Spring Framework 6 having some #Configuration classes with the #Profile("default") and #Profile("prod") respectively. Exists the following scenarios:
Scenario I
ctx = new AnnotationConfigApplicationContext("com.manuel.jordan.config");
When is executed the Main class with the -Dspring.profiles.default=default,prod property, through:
System.out.println("Default Profiles");
for(String defaultProfile : ctx.getEnvironment().getDefaultProfiles()) {
System.out.println(" " + defaultProfile);
}
I can see both profiles being listed and is possible retrieve the #Bean from these #Configuration classes based with the #Profile("default") and #Profile("prod") profiles. Until here all work as expected. Therefore is confirmed that these two default profiles were applied to the Spring Application Context for the beans creation process.
Scenario II
With
ctx = new AnnotationConfigApplicationContext("com.manuel.jordan.config");
ctx.getEnvironment().setDefaultProfiles("default", "prod");
or even with
ctx = new AnnotationConfigApplicationContext();
ctx.getEnvironment().setDefaultProfiles("default", "prod");
ctx.scan("com.manuel.jordan.config");
ctx.refresh();
When is executed the Main class without the -Dspring.profiles.default=default,prod property, because now is used .setDefaultProfiles("default", "prod") instead, through:
System.out.println("Default Profiles");
for(String defaultProfile : ctx.getEnvironment().getDefaultProfiles()) {
System.out.println(" " + defaultProfile);
}
I can see both profiles being listed but is not possible retrieve the #Bean from these #Configuration classes, again, based with the #Profile("default") and #Profile("prod") profiles - both profiles were ignored because each one throws the NoSuchBeanDefinitionException type. Why here is failing?
Question
Why did -Dspring.profiles.default=default,prod work and .setDefaultProfiles("default", "prod") did not work?

Returning a different Tomcat session ID based on URI in Spring

I'm very new to Spring + Tomcat and trying to learn it while working on an existing spring boot (v. 2.6.x) web app running on a Tomcat server (tomcat-embed-core v. 9.0.x).
The application serves URLs of this type:
\{customer}\path\to\resource
\{customer}\newapp\path\to\resource
I can see this reported on the ModelAndView controller annotated with the likes of PostMapping and GetMapping.
Now, the service keeps tracks of sessions using JSESSION cookies and generating session IDs using tomcat's standard org.apache.catalina.SessionIdGenerator.
I would like to programmatically change the format of the session identifier generated by tomcat, depending on the request being served.
Given the paths above, for instance, I'd like to have:
request to 1. above, should be generating session identifiers like 905A6892CB2C12F84A331F58A6A2C382
requests to newapp, i.e. 2. above, should be generating session identifiers like NEWAPP_905A6892CB2C12F84A331F58A6A2C382
The format of the session is irrelevant, but they must have a different prefix.
It seems that one way to achieve this would be to have two different contexts, each one with a different catalina Manager, which can be set using org.apache.catalina.Context#setManager.
I'm not able to define two different contexts because the root in the path is the variable {customer}, nor I'm able to dynamically inject the context using an implementation of org.apache.catalina.valves.ValveBase, as there is no easy way to create a delegated context, where the only difference WRT the base tomcat context is the Manager.
I've tried changing the root of the newapp paths to be like newapp\{customer}\path\to\resource and creating a new Context for newapp, but this fails if I try to override org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory#getWebServer like
#Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
prepareContext(tomcat.getHost(), initializers);
final String documentRoot = getValidDocumentRoot().getAbsolutePath();
final Context ctx = tomcat.addContext("/newapp", documentRoot);
ctx.setManager(new NewAppManager());
return getTomcatWebServer(tomcat);
}
returning a 404 error - it looks like the application is not able to find the mapping for the path, even if I successfully changed the GetMapping to newapp\{customer}\path\to\resource.
Can anyone suggest the best way to achieve this and if what I'm trying even makes sense?
Thank you so much!
I was able to achieve the required outcome by adding a few lines in the getWebServer method.
Adding the whole method below, with comments, for clarity.
#Override
public WebServer getWebServer(ServletContextInitializer... initializers) {
Tomcat tomcat = new Tomcat();
File baseDir = createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
customizeEngine(tomcat.getEngine());
prepareContext(tomcat.getHost(), initializers);
// This is the ROOT context generated by the embedded tomcat
final StandardContext rootCtx = (StandardContext) tomcat.getHost().findChild("");
// Both context will be sharing the same war file
final String documentRoot = getValidDocumentRoot().getAbsolutePath();
// Creating a new context for the "newapp, serving the "/newapp" path
final StandardContext newappCtx = (StandardContext) tomcat.addContext("/newapp", documentRoot);
// This turned out to be very important, sets the classloader of the context to delegate to the application classloader, exactly as ROOT ctx does
newappCtx.setParentClassLoader(rootCtx.getParentClassLoader());
newappCtx.setDelegate(true);
// This was needed in our specific case, the default cookie path would be "/newapp"
newappCtx.setSessionCookiePath("/");
// And finally setting the session manager, generating a new session identifier
newappCtx.setManager(new NewappSessionManager());
final WebServer ws = getTomcatWebServer(tomcat);
// This is the default dispatcher created by the embedded tomcat
final Servlet dispatcherServlet = ((StandardWrapper) rootCtx.findChild("dispatcherServlet")).getServlet();
// We're now adding the default servlet to the "newappCtx"
Tomcat.addServlet(newappCtx, "dispatcherServlet", dispatcherServlet);
// it will serve all paths under "/newapp"
newappCtx.addServletMappingDecoded("/*", "dispatcherServlet");
return ws;
}
....
// Simple manager for the new context
private static final class NewappSessionManager extends StandardManager {
#Override
protected String getNextSessionId() {
return "NEWAPP_" + super.getNextSessionId();
}
}

Activiti Escalation Listener Configuration

I am using activiti 5.18.
Behind the scenes : There are few task which are getting routed though a workflow. Some of these tasks are eligible for escalation. I have written my escalation listener as follows.
#Component
public class EscalationTimerListener implements ExecutionListener {
#Autowired
ExceptionWorkflowService exceptionWorkflowService;
#Override
public void notify(DelegateExecution execution) throws Exception {
//Process the escalated tasks here
this.exceptionWorkflowService.escalateWorkflowTask(execution);
}
}
Now when I start my tomcat server activiti framework internally calls the listener even before my entire spring context is loaded. Hence exceptionWorkflowService is null (since spring hasn't inejcted it yet!) and my code breaks.
Note : this scenario only occurs if my server isn't running at the escalation time of tasks and I start/restart my server post this time. If my server is already running during escalation time then the process runs smoothly. Because when server started it had injected the service and my listener has triggered later.
I have tried delaying activiti configuration using #DependsOn annotation so that it loads after ExceptionWorkflowService is initialized as below.
#Bean
#DependsOn({ "dataSource", "transactionManager","exceptionWorkflowService" })
public SpringProcessEngineConfiguration getConfiguration() {
final SpringProcessEngineConfiguration config = new SpringProcessEngineConfiguration();
config.setAsyncExecutorActivate(true);
config.setJobExecutorActivate(true);
config.setDataSource(this.dataSource);
config.setTransactionManager(this.transactionManager);
config.setDatabaseSchemaUpdate(this.schemaUpdate);
config.setHistory(this.history);
config.setTransactionsExternallyManaged(this.transactionsExternallyManaged);
config.setDatabaseType(this.dbType);
// Async Job Executor
final DefaultAsyncJobExecutor asyncExecutor = new DefaultAsyncJobExecutor();
asyncExecutor.setCorePoolSize(2);
asyncExecutor.setMaxPoolSize(50);
asyncExecutor.setQueueSize(100);
config.setAsyncExecutor(asyncExecutor);
return config;
}
But this gives circular reference error.
I have also tried adding a bean to SpringProcessEngineConfiguration as below.
Map<Object, Object> beanObjectMap = new HashMap<>();
beanObjectMap.put("exceptionWorkflowService", new ExceptionWorkflowServiceImpl());
config.setBeans(beanObjectMap);
and the access the same in my listener as :
Map<Object, Object> registeredBeans = Context.getProcessEngineConfiguration().getBeans();
ExceptionWorkflowService exceptionWorkflowService = (ExceptionWorkflowService) registeredBeans.get("exceptionWorkflowService");
exceptionWorkflowService.escalateWorkflowTask(execution);
This works but my repository has been autowired into my service which hasn't been initialized yet! So it again throws error in service layer :)
So is there a way that I can trigger escalation listeners only after my entire spring context is loaded?
Have you tried binding the class to ApplicationListener?
Not sure if it will work, but equally I'm not sure why your listener code is actually being executed on startup.
Try to set the implementation type of listeners using Java class or delegate expression and then in the class implement JavaDelegate instead of ExecutionListener.

Correct use of Hazelcast Transactional Map in an Spring Boot app

I am working on a proof of concept of Hazelcast Transactional Map. To accomplish this I am writing an Spring Boot app and using Atomikos as my JTA/XA implementation.
This app must update a transactional map and also update a database table by inserting a new row all within the same transaction.
I am using JPA / SpringData / Hibernate to work with the database.
So the app have a component (a JAVA class annotated with #Component) that have a method called agregar() (add in spanish). This method is annotated with #Transactional (org.springframework.transaction.annotation.Transactional)
The method must performe two task as a unit: first must update a TransactionalMap retrieved from Hazelcast instance and, second, must update a database table using a repository extended from JpaRepository (org.springframework.data.jpa.repository.JpaRepository)
This is the code I have written:
#Transactional
public void agregar() throws NotSupportedException, SystemException, IllegalStateException, RollbackException, SecurityException, HeuristicMixedException, HeuristicRollbackException, SQLException {
logger.info("AGRENADO AL MAPA ...");
HazelcastXAResource xaResource = hazelcastInstance.getXAResource();
UserTransactionManager tm = new UserTransactionManager();
tm.begin();
Transaction transaction = tm.getTransaction();
transaction.enlistResource(xaResource);
TransactionContext context = xaResource.getTransactionContext();
TransactionalMap<TaskKey, TaskQueue> mapTareasDiferidas = context.getMap("TAREAS-DIFERIDAS");
TaskKey taskKey = new TaskKey(1L);
TaskQueue taskQueue = mapTareasDiferidas.get(taskKey);
Integer numero = 4;
Task<Integer> taskFactorial = new TaskImplFactorial(numero);
taskQueue = new TaskQueue();
taskQueue.getQueue().add(taskFactorial);
mapTareasDiferidas.put(taskKey, taskQueue);
transaction.delistResource(xaResource, XAResource.TMSUCCESS);
tm.commit();
logger.info("AGRENADO A LA TABLA ...");
PaisEntity paisEntity = new PaisEntity(100, "ARGENTINA", 10);
paisRepository.save(paisEntity);
}
This code is working: if one of the tasks throw an exception then both are rolled back.
My questions are:
Is this code actually correct?
Why #Transactional is not taking care of commiting the changes in the map and I must explicitylly do it on my own?
The complete code of the project is available en Github: https://github.com/diegocairone/hazelcast-maps-poc
Thanks in advance
Finally i realized that i must inject the 'UserTransactionManager' object and take the transaction from it.
Also is necessary to use a JTA/XA implementation. I have chosen Atomikos and XA transactions must be enable in MS SQL Server.
The working example is available at Github https://github.com/diegocairone/hazelcast-maps-poc on branch atomikos-datasource-mssql
Starting with Hazelcast 3.7, you can get rid of the boilerplate code to begin, commit or rollback transactions by using HazelcastTransactionManager which is a PlatformTransactionManager implementation to be used with Spring Transaction API.
You can find example here.
Also, Hazelcast can participate in XA transaction with Atomikos. Here's a doc
Thank you
I have updated to Hazelcast 3.7.5 and added the following code to HazelcastConfig class.
#Configuration
public class HazelcastConfig {
...
#Bean
public HazelcastInstance getHazelcastInstance() {
....
}
#Bean
public HazelcastTransactionManager getTransactionManager() {
HazelcastTransactionManager transactionManager = new HazelcastTransactionManager(getHazelcastInstance());
return transactionManager;
}
#Bean
public ManagedTransactionalTaskContext getTransactionalContext() {
ManagedTransactionalTaskContext transactionalContext = new ManagedTransactionalTaskContext(getTransactionManager());
return transactionalContext;
}
When I run the app I get this exception:
org.springframework.beans.factory.NoSuchBeanDefinitionException: No
bean named 'transactionManager' available: No matching
PlatformTransactionManager bean found for qualifier
'transactionManager' - neither qualifier match nor bean name match!
The code is available at Github on a new branch: atomikos-datasource-mssql-hz37
Thanks in advance

How to autowire Quartz JobListener

I am using Spring annotations to wire my application dependencies.
As far as I can tell, there is no way to inject my JobListener to the Quartz SchedulerFactoryBean as it is configured here:
#Bean(name="schedulerFactory")
public SchedulerFactoryBean schedulerFactoryBean() throws Exception {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
schedulerFactoryBean.setApplicationContextSchedulerContextKey("applicationContext");
schedulerFactoryBean.setConfigLocation(new ClassPathResource("quartz.properties"));
schedulerFactoryBean.setDataSource(dataSource);
// This call results in the error:
// java.lang.IllegalStateException: Non-global JobListeners not supported on
// Quartz 2 - manually register a Matcher against the Quartz ListenerManager instead
//schedulerFactoryBean.setJobListeners(new JobListener[] { jobActivityListener() });
return schedulerFactoryBean;
}
I am currently having to programmatically configure the JobListener to be created when the job is first triggered:
ListenerManager listenerManager = scheduler.getListenerManager();
if (listenerManager.getJobListener(jobKey.getName()) == null) {
logger.debug("ADDING JOB LISTENER FOR " + jobKey.getName());
listenerManager.addJobListener(new JobActivityListener(), keyEquals(jobKey));
}
This is not ideal, as we would like Spring to manage the dependencies, and inject the dependencies needed by the JobListener.
Is there any information on configuring the job listeners via Spring?
Well, it's not exactly related to the job listener, but ultimately if you want to inject the listener via the #autowired location, in your xml configura file you have to define simply the mapping (do care about the service/component name ) and that should do the trick..
It' show i inject service in Quartz job actually.

Resources