Can I create a CronJob and a Job in the web context? - ehcache

Normally, we can create CronJobs and Jobs (which extends AbstractJobPerformable) by definining configuration in *-items.xml and in *-spring.xml. However, I can't get these jobs to access the beans in the web context. Can I create a CronJob and a Job in the web context? If yes, how?
The reason I need the jobs to work in the web context is I need to access the ehcache, and manipulate it.

You don't need to create the job in your web context. Create an own extension for this job and a dependency to your facades extension.

If you'd like to keep using default cronJobService to run your jobs then, probably, the only solution would be to 'correctly' get access to a required web context.
Sample groovy script to get access to web application context by its name.
import org.springframework.cache.ehcache.EhCacheCacheManager
import org.springframework.web.context.ContextLoader
final String WEB_CONTEXT_NAME = "/rest"
def contextLoader = ContextLoader.currentContextPerThread.find { it.key.contextName == WEB_CONTEXT_NAME }
assert contextLoader != null
def webApplicationContext = contextLoader.value
webApplicationContext.getBean(EhCacheCacheManager)
Keep in mind that ContextLoader.currentContextPerThread is a private field. To access the field in java you can use
def f = ContextLoader.getDeclaredField("currentContextPerThread");
f.setAccessible(true);
Map<ClassLoader, WebApplicationContext> contexts = f.get(HybrisContextLoaderListener);
Sample JobPerformable will be looking like
public class WebContextAwareJob extends AbstractJobPerformable<CronJobModel> {
#Override
public PerformResult perform(final CronJobModel cronJobModel) {
final CacheManager cacheManager = getEhCacheManager().getCacheManager();
final Cache cache = cacheManager.getCache("");
}
private EhCacheCacheManager getEhCacheManager() {
return getRegistry().getBean(EhCacheCacheManager.class)
}
private WebApplicationContext getRegistry() {
<see sample code above>
}
}

Related

How do I get JobRunr to detect my scheduled background job in a Spring controller/service?

I have been looking into using JobRunr for starting background jobs on my Spring MVC application, as I really like the simplicity of it, and the ease of integrating it into an IoC container.
I am trying to create a simple test scheduled job that writes a line of text to my configured logger every minute, but I'm struggling to figure out how to get the JobRunr background job server to detect it and queue it up. I am not using Spring Boot so I am just using the generic jobrunr Maven artifact rather than the "Spring Boot Starter". My setup is as follows:
pom.xml
<dependency>
<groupId>org.jobrunr</groupId>
<artifactId>jobrunr</artifactId>
<version>2.0.0</version>
</dependency>
ApplicationConfig.java
#Bean
public JobMapper jobMapper() {
return new JobMapper(new JacksonJsonMapper());
}
#Bean
#DependsOn("jobMapper")
public StorageProvider storageProvider(JobMapper jobMapper) {
InMemoryStorageProvider storageProvider = new InMemoryStorageProvider();
storageProvider.setJobMapper(jobMapper);
return storageProvider;
}
#Bean
#DependsOn("storageProvider")
public JobScheduler jobScheduler(StorageProvider storageProvider, ApplicationContext applicationContext) {
return JobRunr.configure().useStorageProvider(storageProvider)
.useJobActivator(applicationContext::getBean)
.useDefaultBackgroundJobServer()
.useDashboard()
.useJmxExtensions()
.initialize();
}
BackgroundJobsController.java
#Controller
public class BackgroundJobsController {
private final Logger logger = LoggerFactory.getLogger(getClass());
private #Autowired JobScheduler jobScheduler;
#Job(name = "Test")
public void executeJob() {
BackgroundJob.scheduleRecurrently(Cron.minutely(), () -> logger.debug("It works!"));
jobScheduler.scheduleRecurrently(Cron.minutely(), () -> logger.debug("It works too!"));
}
}
As you can see, I have tried both methods of initiating the background job in the executeJob method. The issue is basically getting Jobrunr to detect the jobs - is it simply a case of somehow triggering the executeJob method upon startup of the application? If so, does anyone know the most simple way to do that? Previously I have used the Spring #Scheduled annotation to automatically run through methods in a Service/Controller class upon startup of the application, so I was hoping there was a straightforward way to get Jobrunr to pick up the scheduled tasks I am trying to create. Apologies if it is something stupid that I have overlooked. I've spent a good few hours trying different things and reading through the documentation!
Thanks in advance!
There are different ways for doing so:
This is one, annotating a method with #PostConstruct is indeed another.
#SpringBootApplication
#Import(JobRunrExampleConfiguration.class)
public class JobRunrApplication {
public static void main(String[] args) {
ConfigurableApplicationContext applicationContext = SpringApplication.run(JobRunrApplication.class, args);
JobScheduler jobScheduler = applicationContext.getBean(JobScheduler.class);
jobScheduler.<SampleJobService>scheduleRecurrently("recurring-sample-job", every5minutes(), x -> x.executeSampleJob("Hello from recurring job"));
}
}
You can see an example here: https://github.com/jobrunr/example-java-mag/blob/main/src/main/java/org/jobrunr/examples/JobRunrApplication.java
Have you tried annotating your executeJob Method with a #PostConstruct ? That way upon initialisation of your application, the jobs would be registered to the JobServer.
I believe the #Job annotation is meant fo the method of the job itself. (In your case the debug method).
There is now a new way to do so:
You can add #Recurring to any Spring Boot, Micronaut or Quarkus bean method. A Spring Boot example:
#Component
public class SomeService {
#Recurring(id="recurring-job-every-5-min" interval = "PT5M")
#Job(name="job name for the dashboard")
public void runEvery5Minutes() {
// business logic comes here
}
}
For more info, see the JobRunr documentation.

How to initiate a global method that can be accessed by all controllers in Spring Boot

I would like to initiate a simple POJO that generates an array of random Strings when initiated (or when the Spring Boot application starts). This array of Strings has to be shared across every controller in the Spring Boot application, and it cannot be different for different controllers. The class and methods (of this shared POJO) are internal to a Spring Boot application and are accessed by calling the getters in the controller methods (only).
Furthermore, I would like to avoid using the application.properties. The best solution would be java-only (no database such as H2 or offloading the POJO onto a file). Also, using the sessions will not help.
Something like this would help:
http://www.masterspringboot.com/security/authentication/securing-spring-boot-with-in-memory-basic-authentication
How can I accomplish such a design ?
My idea is to simply use a micro-service and launch it separately, but I would like to confirm if there is something else I can do within the single Spring Boot application.
You can create a singleton class that holds your values:
public class Main {
public static void main(String[] args) {
DataHolder dataHolder = DataHolder.getInstance();
String[] arr = dataHolder.getArr();
}
}
class DataHolder {
private static DataHolder instance = null;
private String[] arr = new String[10];
public static DataHolder getInstance() {
if (instance == null)
instance = new DataHolder();
return instance;
}
private DataHolder() {
fillArray();
}
private void fillArray() {
// use this method to fill your array
}
public String[] getArr() {
return arr;
}
}
To answer my own question, the solution was to use the in-memory provided by the mapDB, and generate all the data during the spring-boot initiating itself...
https://mvnrepository.com/artifact/org.mapdb/mapdb
The source code example can be found using the search engines...

Dependency Injection with dynamically instanciated class with Spring Boot

I'm trying to develop a spring-boot application which offer the possibility for the user to create and call some simple workflows.
The steps of the workflows are already written (they all extends the same class), and, when the user create a workflow, he/she just pick which steps he wants to include in his it. The steps and the workflows are saved in a database.
My problem comes when the user call the workflow: I want to instanciate dynamically each step using the class loader but with the dependencies injected by spring!
Here is an example of a plug-in:
public class HelloWorldStepPlugin extends StepPlugin {
private static final Logger LOG = LogManager.getLogger();
#Autowired
private HelloWorldRepository repository;
public HelloWorldStepPlugin() {
super(HelloWorldStepPlugin.class.getSimpleName());
}
#Override
public void process() {
LOG.info("Hello world!");
this.repository.findAll(); // <= throw a NullPointerException because this.repository is null
}
}
Here is how I execute a Workflow (in another class):
ClassLoader cl = getClass().getClassLoader();
for (Step s : workflow.getSteps()) {
StepPlugin sp = (StepPlugin) cl.loadClass(STEP_PLUGIN_PACKAGE + s.getPlugin()).newInstance();
sp.process();
}
How can I do to have my HelloWorldRepository injected by Spring?
Is there a much better approach to do what I intend to?
I suggest you declare your steps as prototype beans. Instead of saving class names in the database, save bean names. Then get the steps and the plugins from the spring context (i.e. using getBean()).

OSGi force bundle start twice with different configurations

I'm using embedded Felix in my application. Application can potentially deal with lot of plugins that exposes similar interface IFoo. There is default an implementation FooImpl Hopefully for most plugins default FooImpl can be used with specific configuration files.
I would like dynamically install and start the same bundle (with FooImpl) when new configuration file appears. I've reviewed already FileInstall but have no idea how to apply it there.
UPDATE: Deployment sequence. The jar containing FooImpl and IFoo is stable, but I need hot-deploy of new instances that are result of uploading new .cfg file to scope of FileInstall. So desired is very simple - user uploads .cfg, new service (instance of FooImpl) is appeared.
Using Factory Configurations would allow you to create different instances of FooImpl based on different configurations.
For example in Declarative Services you can create a component like
import org.apache.felix.scr.annotations.*;
import org.apache.sling.commons.osgi.PropertiesUtil;
#Component(metatype = true,
name = FooImpl.SERVICE_PID,
configurationFactory = true,
specVersion = "1.1",
policy = ConfigurationPolicy.REQUIRE)
public class FooImpl implements IFoo
{
//The PID can also be defined in interface
public static final String SERVICE_PID = "com.foo.factory";
private static final String DEFAULT_BAR = "yahoo";
#Property
private static final String PROP_BAR = "bar";
#Property(intValue = 0)
static final String PROP_RANKING = "ranking";
private ServiceRegistration reg;
#Activate
public void activate(BundleContext context, Map<String, ?> conf)
throws InvalidSyntaxException
{
Dictionary<String, Object> props = new Hashtable<String, Object>();
props.put("type", PropertiesUtil.toString(config.get(PROP_BAR), DEFAULT_BAR));
props.put(Constants.SERVICE_RANKING,
PropertiesUtil.toInteger(config.get(PROP_RANKING), 0));
reg = context.registerService(IFoo.class.getName(), this, props);
}
#Deactivate
private void deactivate()
{
if (reg != null)
{
reg.unregister();
}
}
}
Key points here being
You use a component of type configurationFactory
In the activate method you read the config and then based on that register a service
In deactivate you explicitly unregister the service
End users would then create config file with name <pid>-<some name>.cfg. Then DS would then activate the component.
Then you can create multiple instances by creating configuration (using File Install like) file with name <pid>-<some name>.cfg like com.foo.factory-type1.cfg
Refer to JdbcLoginModuleFactory and its associated config for one such example.
If you want to achieve the same via plain OSGi then you need to register a ManagedServiceFactory. Refer to JaasConfigFactory for one such example.
Key points here being
You register a ManagedServiceFactory instance with configuration PID as the service property
In the ManagedServiceFactory(String pid, Dictionary properties) callback register instances of FooImpl based on the config properties
Sounds like you want to only have one bundle with FooImpl installed but have it register multiple IFoo services, one for each configuration. Look at Declarative Services and use factory configurations with Config Admin to establish the multiple configurations for the DS component.

Check which beans have loaded in spring context

I am having a series of odd errors in testing and deployment. They seem to indicate that some of my beans are not loading into the context, despite them being defined in applicationContext.xml.
Is there any way to check during testing which beans have actually been loaded? Or to find a complete list of beans loaded at run time?
Thanks,
b
At startup, Spring logs at info level the names of all the beans being loaded by a context. Or in code, you can use getBeanDefinitionNames() to get all the bean names.
if there is more than one context say if you are using spring mvc you can use something more powerful like this.
public class SampleContextApplicationListener implements ApplicationListener<ApplicationContextEvent> {
private Map<String,ApplicationContext> contextMap = new Hashtable<String,ApplicationContext>();
#Override
public void onApplicationEvent(ApplicationContextEvent event) {
if( event instanceof ContextStartedEvent || event instanceof ContextRefreshedEvent){
this.getContextMap().put(event.getApplicationContext().getDisplayName(), event.getApplicationContext());
}
}
public Map<String,ApplicationContext> getContextMap() {
return contextMap;
}
}
You can then inject the listener where it is needed, and extract the map of contextens and then interogate it for all its bean, using the getBeanDefinitionNames()
#Autowired
private StatusTestsApplicationListener listener;

Resources