Saying in a nutshell I would like to put in custom scope particular instance of Configuration class from rest request. Main problem is that custom scope (JobScoped from JBeret h
I think this question consists of several parts:
I will try to answer all individual questions, but keep in mind that I've only very recently started using CDI/Weld, and have no experience with JBeret.
The reason I am adding this question, is because I think Configuration
may not need to be a scoped entity. If Configuration
has nothing specific to the scope, it could be @Singleton
or @Stateless
as well. Think for example from configuration files, resources, or environment variables, that will not change on runtime. Non-scoped (or Singleton-scoped) dependencies can be injected into batchlets just fine, using regular @Inject
fields, without any need for a @JobScoped
annotation.
So what if the actual value depends on the context and cannot be injected in a @Singleton
fashion? Based from the JBeret documentation, it is preferred to pass all configuration by Properties
. These can then be read from the JobContext
, or injected using the @BatchProperty
annotation. This only works for a predefined list of types that are serialisable from a String.
@Named
public class MyBatchlet extends AbstractBatchlet {
@Inject
@BatchProperty(name = "number")
int number;
}
@RequestScope
in a batch job?I think you shouldn't. The @RequestScope
is for requests solely. If you have dependencies dependent on @RequestScope
that should be accessible outside of a request, consider to introduce a custom scope.
If you really need to enter the
@RequestScope
programatically, you can define your own context for it and enter that context (see part 4 below) or enter the context by default, as addressed in this blogpost by Dan Haywood, in his attempt to get into the@RequestScope
in Java SE.
It is fairly easy to create a custom scope. A custom scope however requires an implementation for the scope context. I found this to be a little unclear in the documentation. Luckily there is the library microscoped library. For this example, you only need the microscoped-core
dependency, which provides a ScopeContext
implementation that is used in their custom scopes. We will use that ScopeContext
for our simple scope as well.
First we have to create the Scope annotation:
@Documented
@Scope
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface CustomScoped {}
Secondly, we have to create an extension:
public class CustomScopedExtension implements Extension, Serializable {
public void addScope(@Observes final BeforeBeanDiscovery event) {
event.addScope(CustomScoped, true, false);
}
public void registerContext(@Observes final AfterBeanDiscovery event) {
event.addContext(new ScopeContext<>(CustomScoped.class));
}
}
Note that we're using the ScopeContext from microscoped here. Furthermore, you should register your extension by adding the full classname to
META-INF/services/javax.enterprise.inject.spi.Extension`.
Now we need to enter our scope. We can do this with a little bit of code, that you can place for example in a web Filter
or method interceptor. The code uses an BeanManager
instance, which can be obtained with @Inject
:
ScopeContext<?> context = (ScopeContext<?>) beanManager.getContext(CustomScoped.class);
context.enter(key);
try {
// continue computation
} finally {
context.destroy(key);
}
I have been asking myself the very same question, and this is the solution I came up with. See also my question on how to properly seed from custom Weld CDI scopes: Seed value in Weld CDI custom scope . I do have a workaround for your issue though:
@Singleton
public class ConfigurationProducer {
private final InheritableThreadLocal<Configuration> threadLocalConfiguration =
new InheritableThreadLocal<>();
@Produces
@ActiveDataSet
public ConfigurationConfiguration() {
return threadLocalConfiguration.get()
}
public void setConfiguration(Configuration configuration) {
threadLocalConfiguration.set(configuration);
}
}
Now from your the interceptor written above, you can inject ConfigurationProducer
and use ConfigurationProducer #setConfiguration(Configuration)
to set the Configuration
for the current thread. I am still looking for better options here.
The batch spec (JSR 352) defines a standard way to pass user object within a job, by calling:
javax.batch.runtime.context.JobContext#setTransientUserData(myObject);
For simple cases, this should suffice. You can define a job listener, inject JobContext
into your job listener class, and inside its startJob()
method, set transient user data. It will then be available to the entire job execution, and can be rest to other values. For more complex use cases, @JobScoped
is a better choice.
Firstly I would like to thank you again Jan-Willem Gmelig Meyling because your answer was very helpful. Anyway, I wanted to use given scope by JBeret which is JobScoped, today it could be only used on TYPE level. I did similary workaround as Jan-Willem Gmelig Meyling suggested but:
Solution:
1) Configuration class:
@JobScoped
public class Configuration
{...}
2) At JobListener magic happens. Additional comments are redundant.
Let's my code speak for itself ;)
import javax.batch.api.listener.AbstractJobListener;
public class MyJobListener extends AbstractJobListener{
@Inject
private Configuration jobScopedConfiguration;
@Override
public void beforeJob() throws Exception {
enrichJobScopedConfigurationWithRequestConfiguration();
...
super.beforeJob();
}
private void enrichJobScopedConfigurationWithRequestConfiguration(){
Configuration requestConfiguration =
(Configuration) BatchRuntime.getJobOperator().getJobExecution(currentExecutionId).getJobParameters()
.get("configuration");
jobScopedConfiguration.updateWith(requestConfiguration);
}
Now I can Inject my Configuration in any jberet/java batch artifact in context of job, e.g.:
public class MyReader implements ItemReader {
@Inject
private Configuration configFile;
}