Why the need of a specific Spring FXML Loader - spring

I am looking at this example: Building Applications in JavaFX 2.0 and they show a custom SpringFxmlLoader:
import java.io.IOException;
import java.io.InputStream;
import javafx.fxml.FXMLLoader;
import org.springframework.context.ApplicationContext;
public class SpringFxmlLoader
{
private ApplicationContext context;
public SpringFxmlLoader(ApplicationContext context)
{
this.context = context;
}
public Object load(String url, Class<?> controllerClass) throws IOException
{
InputStream fxmlStream = null;
try
{
fxmlStream = controllerClass.getResourceAsStream(url);
Object instance = context.getBean(controllerClass);
FXMLLoader loader = new FXMLLoader();
loader.getNamespace().put("controller", instance);
return loader.load(fxmlStream);
}
finally
{
if (fxmlStream != null)
{
fxmlStream.close();
}
}
}
}`
Why does one need to create a specific spring FXML Loader? I mean, even with a simple fxml loader, when you load a fxml like this:
AnchorPane page = (AnchorPane) FXMLLoader.load(TabePaneGraph.class.getResource("Sample.fxml")); the Sample Controller is called anyways and any initialization is still done. I am trying to understand the motivation behind this specific custom SpringFxmlLoader implementation.

There are multiple ways to skin a cat. I'm guessing the motivation to use spring in that article is because many web developers would be familiar with it. It also might make it look more like an alternative to a Java EE application. Which it is, but not because you can use spring with it.
You don't need any dependency injection framework to develop with JavaFX, in fact we need to look carefully at our dependencies because they will add to download time if you are expecting users to download your application.

There are (at least) 2 ways, how to specify a controller:
declare the controller class in the FXML file: note that you specify
the class not the instance here. The FXMLoader will create a new
instance.
pass an existing instance (e.g. "this" or as here a bean instantiated with Spring) as the controller to the FXMLLoader
loader.getNamespace().put("controller", instance);
I'm not sure about this part, but I think it can be replaced with setController() in the latest JavaFX version.

Related

How to have dynamic base URL with Quarkus MicroProfile Rest Client?

Quarkus using Rest Client, explains how to use the MicroProfile REST Client. For Base URL application.properties can be used.
org.acme.restclient.CountriesService/mp-rest/url=https://restcountries.eu/rest #
With above approach, cant have dynamic base URL.
Able to achieve it by using RestClientBuilder as explained in MicroProfile Rest Client. Downside of this approach is not having auto-negotiation capability.
SimpleGetApi simpleGetApi = RestClientBuilder.newBuilder().baseUri(getApplicationUri()).build(SimpleGetApi.class);
Is there other or better way to achieve this? Thanks.
While it is true, that the MP Rest CLient does not allow you to set the BaseUri dynamically when you use declarative/Injected clients, there are some (albeit hacky) ways how to achieve that.
One is to use standard ClientRequestFilter which can modify the URL:
#Provider
#Slf4j
public class Filter implements ClientRequestFilter {
#Inject RequestScopeHelper helper;
#Override
public void filter(ClientRequestContext requestContext) throws IOException {
if (helper.getUrl() != null) {
URI newUri = URI.create(requestContext.getUri().toString().replace("https://originalhost.com", helper.getUrl()));
requestContext.setUri(newUri);
}
}
}
Where RequestScopeHelper is some help class (e.g. request scoped bean) through which you can pass the dynamic url, for example:
#Inject
RequestScopeHelper helper;
#Inject
#RestClient
TestIface myApiClient;
public void callSomeAPIWithDynamicBaseUri(String dynamic) {
helper.setUrl(dynamic);
myApiClient.someMethod();
}
Second is to use MP rest client SPI, namely the RestClientListener which allows you to modify the rest clients after they are built.
For this to work, you have to set the scope of your rest client to RequestScoped so that new instance is created for each request(if you use singleton for example, then the client is only created once and your listener will only be called once). This you can do via quarkus properties:
quarkus.rest-client."com.example.MyRestIface".scope=javax.enterprise.context.RequestScoped
public class MyListener implements RestClientListener {
#Override
public void onNewClient(Class<?> serviceInterface, RestClientBuilder builder) {
String newUri = //obtain dynamic URI from somewhere e.g. again request scope bean lookup, or maybe dynamic config source (create new in-memory ConfigSource, before you invoke your rest client set the corresponding rest client url property to your dynamic value, then inside this listener use ConfigProvider.getConfig().getProperty...)
builder.baseUri(URI.create(newUri));
}
}
Don't forget to register this listener as service provider(META-INF/services/org.eclipse.microprofile.rest.client.spi.RestClientListener)
Another option is to use custom CDI producer that would produce the Rest client instances for you; then you could control all client config yourself. You can use the RestClientBase from Quarkus rest client which is exactly what Quarkus uses under the hood during deployment phase to construct client instances. You will however have to duplicate all the logic related to registration of handlers, interceptors etc.
Do keep in mind, that any of these solutions will make the debugging and problem analysis more challenging - because you will now have multiple places, where the URI is controlled(MP config/quarkus properties, env vars, your custom impl...), so you need to be careful with your approach and maybe add some explicit log messages when you override the URI manually.
MicroProfile REST Client in Quarkus does allow you to use dynamic base URL with that simple "hack" :
Just put an empty String in #Path annotations for you API interface like that :
import javax.ws.rs.GET;
import javax.ws.rs.Path;
#Path("")
public interface SimpleGetApi {
#Path("")
#GET
String callWithDynmamicUrl(); //it can be String or any return type you want
}
After that you are ready to call your dynamic base URL :
import org.eclipse.microprofile.rest.client.RestClientBuilder;
import java.net.URI;
public class Example {
public static void main(String[] args) {
URI anyDynamicUrl = URI.create("http://restcountries.eu/rest/some/dynamic/path");
SimpleGetApi simpleGetApi = RestClientBuilder.newBuilder().baseUri(anyDynamicUrl)
.build(SimpleGetApi.class);
simpleGetApi.callWithDynmamicUrl();
}
}

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()).

Spring - Retrieve all scanned packages

I'm creating a Spring Starter project and need to get all classes which are marked with a custom annotation. The annotated class is not a spring bean.
My current solution is to use the ClassPathScanningCandidateComponentProvider to find the required classes.
ClassPathScanningCandidateComponentProvider scanner =
new ClassPathScanningCandidateComponentProvider(false);
scanner.addIncludeFilter(new AnnotationTypeFilter(CustomAnnotation.class));
candidates = scanner.findCandidateComponents("THE MISSING PACKAGE NAME");
The problem is that I'm currently provide an empty package String so that all packages/classes are scanned which slows the startup down.
I need to access the packages which are scanned by Spring to avoid the scanning of all packages and classes.
Is there a way to retrieve all packages programmatically which are scanned by Spring or is there an alternative solution to retrieve custom annotated classes which are not Spring beans.
Greets
One solution without the need to make a full classpath scan is to use the AutowiredAnnotationBeanPostProcessor:
private List<Class<?>> candidates = new ArrayList<>();
#Override
public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
if(beanClass.isAnnotationPresent(YourAnnotation.class)){
candiates.add(beanClass));
System.out.println(beanClass);
return new Object();
}
}
#Bean
public CandiateHolder candidates() {
return new CandidateHolder(candidates);
}
You can check if the bean class which should be instantiated has the required annotation. If its the case you add the class to a property to expose it later as a bean. Instead of returning null you have to return an instance of a new Object. The returned object can be used to wrap the class in a proxy. Cause I don't need an instance I will return a simple new object. Its maybe a dirty hack but it works.
I have to use this kind of hack cause an instantiation of the needed object will result in an runtime error cause it has to be instantiated in the framework I use.

Spring constructor injection with FXML

I have searched for the answer, but i could not found too much.
What i have found: http://steveonjava.com/javafx-in-spring-day-2/
That is a good post about how to use spring for controllers, but it say that you cannot use constructor injection. That would not be so big pain, it is just not so clean for me.
The issue come up when i would like to use custom controls (or custom components). Custom controls are created by javafx so that will not be in spring context.
The issue is that with the given solution only the controllers will be created by spring. I have found a possible way that can be done. thanks to https://www.javacodegeeks.com/2012/04/fxml-custom-components-using.html articel. But i would like to generalize that solution. So the plan is to write my custom BuilderFactory and Builder implementations, which is required a lot of reflection.
What do you think about this approach?
Every idea is welcome
You can certainly use a BuilderFactory to do this. The code is not too bad at all, because the default builder factory is implemented in public API as JavaFXBuilderFactory. Thus you can simply delegate to that if there is no bean of the appropriate type in the application context.
Basically:
private Parent loadFXML(ApplicationContext applicationContext, URL fxmlLocation)
throws IOException {
FXMLLoader loader = new FXMLLoader(fxmlLocation);
// load controllers from application context:
loader.setControllerFactory(applicationContext::getBean);
// load controls from application context, where available:
loader.setBuilderFactory(new BuilderFactory() {
JavaFXBuilderFactory defaultFactory = new JavaFXBuilderFactory();
#Override
public javafx.util.Builder<?> getBuilder(Class<?> type) {
String[] beanNames = applicationContext.getBeanNamesForType(type);
if (beanNames.length == 1) {
return new javafx.util.Builder<Object>() {
#Override
public Object build() {
return applicationContext.getBean(beanNames[0]);
}
};
} else {
return defaultFactory.getBuilder(type) ;
}
}
});
return loader.load();
}

AEM:OSGI sling service activate method not being executed

I am trying to create a very basic sling service in AEM:
package com.mypackage;
/**
* A simple service interface
*/
public interface TestService {
/**
* #return the name of the underlying JCR repository implementation
*/
public String getPropertyName();
}
The implementation class:
package com.mymypackage.impl;
import javax.jcr.Repository;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.Reference;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.jcr.api.SlingRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mypackage.TestService;
#Component(label = "Title", description = "Description.", immediate = true, metatype = true, policy = ConfigurationPolicy.REQUIRE)
#Service(value = {TestService.class})
#Properties({
#Property(name = "propertyPath", label = "Property Label", description = "Property Desc.")
})
public class TestServiceImpl implements TestService {
private static final Logger log = LoggerFactory.getLogger(TestServiceImpl.class);
String propertyPath = null;
#Activate
public void activate(ComponentContext ctx) {
Dictionary properties = ctx.getProperties();
String propertyPath =(String)properties.get("propertyPath");
log.info("====================getPropertyName activate========================"+propertyPath);
this.propertyPath = propertyPath;
}
#Override
public String getPropertyName() {
// TODO Auto-generated method stub
log.info("====================getPropertyName========================"+propertyPath);
return propertyPath;
}
}
and I have created a node of type sling:OsgiConfig inside the config folder. The name of this node is com.mypackage.impl.TestServiceImpl.xml and are the content of it:
<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
jcr:primaryType="sling:OsgiConfig"
propertyPath="http://www.myuhc.com"/>
and this is how I am trying to use it inside a Java class:
public static String getTestService() {
TestService testService = new TestServiceImpl();
String prop = testService.getPropertyName();
return prop;
}
This method is being called from a JSP using customtaglib (method being mapped through a .tld file)
When I use this approach the activate method inside the HelloServiceImpl class is not called and hence the property is not set. But when I use the service inside a component JSP like this:
<%#page import="org.osgi.service.cm.ConfigurationAdmin"%>
<%#page import="org.osgi.service.cm.Configuration"%>
<%
Configuration conf = sling.getService(org.osgi.service.cm.ConfigurationAdmin.class).getConfiguration("Name of the config");
String myProp = (String) conf.getProperties().get("property key");
%>
everything works fine. There is something really wrong I must be doing while trying to call the service from a Java class. How can I use that approach. I don't want to use scriptlet inside JSP. Also, any best practices would be highly appreciated.
Thanks in advance
OSGi Services work within a life cycle which manages the creation and deletion of the Service instances.
As part of the container management of these instances, when the container creates the instance it calls the activation method with the appropriate values. So that the property can be assigned.
What you are doing in your first code snippet:
TestService testService = new TestServiceImpl();
String prop = testService.getPropertyName();
Is not using the containers version of your component. Your using the direct implementation and bypassing the container management.
To use the instance that is managed by the container. You need to request it from the container.
Which is exactly what your second snippet shows
sling.getService(org.osgi.service.cm.ConfigurationAdmin.class)
Is requesting the best matching service from the container.
To access a service from the container. You either need to be a Service yourself. Which you can do by the #Reference annotation.
You mentioned however being in a taglib which makes things a bit more complicated. You need to obtain a reference to the SlingScriptHelper which can be obtained from the pageContext like this;
ServletRequest request = pageContext.getRequest();
final SlingBindings bindings = (SlingBindings) request
.getAttribute(SlingBindings.class.getName());
final SlingScriptHelper scriptHelper = bindings.getSling();
TestService service= scriptHelper
.getService(com.mymypackage.impl.TestService.class);
The problem is, that sometimes when you try to re-install a bundle, some old compiled classess are not removed and replaced with a new one. Try to remove the /var/classes and/or /var/clientlibs nodes and re-install your projects.
This may also be useful, if you are unable to use #Reference annotation for injecting your service
// get the TestServiceImpl.java service component
TestServiceImpl testService = getSlingScriptHelper().getService(TestServiceImpl.class);
//com.adobe.cq.sightly.WCMUsePojo.getSlingScriptHelper()

Resources