EL autocomplete / code assist with Eclipse and Spring Beans - spring

In Eclipse, auto-complete for JSF / EL only works for legacy #ManagedBean or CDI beans (#Named), at least when using the JBoss tools plugin.
See also: EL proposals / autocomplete / code assist in Facelets with Eclipse or Eclipse autocomplete (content assist) with facelets (jsf) and xhtml or Content Assist for JSF2 + CDI (weld) beans + Eclipse Helios
=> Summing-up:
- install JBoss tools JSF + CDI (http://download.jboss.org/jbosstools/oxygen/stable/updates/, JBoss Web and Java EE / JBoss Tools JSF + Visual Page Editor + Contexts and Dependency Injection Tools);
- in project properties: remove Project Facets/"JavaServer Faces" so the very slow Eclipse JSF-autocompleter will not be used, activate CDI / CDI support.
But there is no support when using Spring, i.e. #Controller or #Component.
Typically, you should use CDI beans with full support for all JSF scopes now, but you may have your reasons or an existing project may use Spring.
See also: Moving JSF Managed Beans to Spring beans or https://www.beyondjava.net/blog/integrate-jsf-2-spring-3-nicely/
So, how to support JSF/EL auto-complete for Spring web beans?

I digged into the JBoss tools implementation and a small change makes Spring users happy.
:-)
There is a solution based on the JSF tools (first) and an alternative based on the CDI tools (afterwards).
The following is based on jbosstools-4.5.2.Final using the plugin file org.jboss.tools.jsf_3.8.200.v20170908-0911.jar
But the changes should be the same or very similar for other versions (the relevant source files have their last changes back in Dec 2011 or Sept 2012).
The class org.jboss.tools.jsf.jsf2.bean.model.impl.AbstractMemberDefinitionhas to be extended in the methods getManagedBeanAnnotation() and isAnnotationPresent():
If #ManagedBean is not found, then also look for #Controller (which should be used in Spring, so #Service etc. is not offered in JSF). But this may easily be adjusted, see comments in the following source. Additionally, Spring uses the value annotation attribute instead of name - this is solved via a wrapper class.
public boolean isAnnotationPresent(String annotationTypeName) {
//TW: added Spring annotations
boolean b = (getAnnotation(annotationTypeName) != null);
if (!b && JSF2Constants.MANAGED_BEAN_ANNOTATION_TYPE_NAME.equals(annotationTypeName)) {
b = (getAnnotation("org.springframework.stereotype.Controller") != null);
/* with support for all Spring annotations:
b = (getAnnotation("org.springframework.stereotype.Controller") != null
|| getAnnotation("org.springframework.stereotype.Service") != null
|| getAnnotation("org.springframework.stereotype.Repository") != null
|| getAnnotation("org.springframework.stereotype.Component") != null);
*/
}
return b;
}
public AnnotationDeclaration getManagedBeanAnnotation() {
AnnotationDeclaration ad = annotationsByType.get(JSF2Constants.MANAGED_BEAN_ANNOTATION_TYPE_NAME);
//TW: added Spring annotations
if (ad != null) return ad;
ad = annotationsByType.get("org.springframework.stereotype.Controller");
/* with support for all Spring annotations:
if (ad == null) ad = annotationsByType.get("org.springframework.stereotype.Service");
if (ad == null) ad = annotationsByType.get("org.springframework.stereotype.Repository");
if (ad == null) ad = annotationsByType.get("org.springframework.stereotype.Component");
*/
if (ad != null) {
// create wrapper to map "value" (used by Spring) to "name" (which is used by #ManageBean)
ad = new AnnotationDeclaration() {
private AnnotationDeclaration wrapped;
AnnotationDeclaration init(AnnotationDeclaration wrappedAD) {
this.wrapped = wrappedAD;
return this;
}
#Override
public Object getMemberValue(String name) {
Object val = wrapped.getMemberValue(name);
if (val == null && "name".equals(name)) {
val = wrapped.getMemberValue(null);
}
return val;
}
#Override
public Object getMemberValue(String name, boolean resolve) {
Object result = null;
if (resolve) {
result = this.getMemberConstantValue(name);
}
if (result == null) {
result = this.getMemberValue(name);
}
return result;
}
#Override
public void setDeclaration(IJavaAnnotation annotation) {
wrapped.setDeclaration(annotation);
}
#Override
public IJavaAnnotation getDeclaration() {
return wrapped.getDeclaration();
}
#Override
public IResource getResource() {
return wrapped.getResource();
}
#Override
public IMemberValuePair[] getMemberValuePairs() {
return wrapped.getMemberValuePairs();
}
#Override
public Object getMemberConstantValue(String name) {
return wrapped.getMemberConstantValue(name);
}
#Override
public Object getMemberDefaultValue(String name) {
return wrapped.getMemberDefaultValue(name);
}
#Override
public IMember getParentMember() {
return wrapped.getParentMember();
}
#Override
public String getTypeName() {
return wrapped.getTypeName();
}
#Override
public IType getType() {
return wrapped.getType();
}
#Override
public int getLength() {
return wrapped.getLength();
}
#Override
public int getStartPosition() {
return wrapped.getStartPosition();
}
#Override
public IAnnotationType getAnnotation() {
return wrapped.getAnnotation();
}
#Override
public IAnnotation getJavaAnnotation() {
return wrapped.getJavaAnnotation();
}
#Override
public IMember getSourceMember() {
return wrapped.getSourceMember();
}
#Override
public IJavaElement getSourceElement() {
return wrapped.getSourceElement();
}
}.init(ad); // class
}
return ad;
}
I offer the two compiled classes (main + one inner class) here for direct download:
AbstractMemberDefinition.class + AbstractMemberDefinition$1.class
I promise a trustworthy compile with just above changes (i.e. without any malicious code or similar, you may check via a decompile with CFR, Procyon, aged JAD or Eclipse-ECD) - you may use them directly or perform the compile by yourself (BTW: Does stack overflow offer file attachments?)
Installation:
Exit Eclipse.
Make a backup copy of the original file
eclipse_home\plugins\org.jboss.tools.jsf_3.8.200.v20170908-0911.jar
(e.g. as *.jar_orig).
Copy the provided classes into org.jboss.tools.jsf_3.8.200.v20170908-0911.jar\org\jboss\tools\jsf\jsf2\bean\model\impl (e.g. via Total Commander or another tool supporting zip/jar handling; you may even use JDKs jar tool). Note: the A...$1.class is a new file.
Start Eclipse again and enjoy!
Go to a JSF page and Type Ctrl+Space after #{ to get a list of beans. Member auto-completion works, too (after #{beanName.), even recursive.
Even Ctrl+click or F3 on the bean name works!
Note: the first auto-completion call needs some seconds for the initial bean disovery.
BTW: For this, there is no need to activate CDI support for the project! (Build is quicker then because no CDI Builder is active.)
Alternatively, you may extend the JBoss tools CDI feature to discover Spring beans. It works the same and additionally they will be listed with Ctrl+Alt+Z (toolbar button Open CDI Named Bean).
Note: I did not check if there are any side effects if the non-CDI Spring beans are discovered as CDI beans!
For this, the file org.jboss.tools.cdi.internal.core.impl.definition.AbstractMemberDefinition has to be extended in the method getNamedAnnotation():
public AnnotationDeclaration getNamedAnnotation() {
AnnotationDeclaration ad = getAnnotation(CDIConstants.NAMED_QUALIFIER_TYPE_NAME);
//TW: added Spring annotations
if (ad != null) return ad;
ad = getAnnotation("org.springframework.stereotype.Controller");
/* add additional Spring annotations, if desired:
if (ad != null) return ad;
ad = getAnnotation("org.springframework.stereotype.Service");
if (ad != null) return ad;
ad = getAnnotation("org.springframework.stereotype.Repository");
if (ad != null) return ad;
ad = getAnnotation("org.springframework.stereotype.Component");
*/
return ad;
}
You have to copy the compiled class (download: CDI-AbstractMemberDefinition.class) into plugins\org.jboss.tools.cdi.core_1.8.201.v20171221-1913.jar\org\jboss\tools\cdi\internal\core\impl\definition
CDI support has to be active for the project.
Maybe someone working for the JBoss tools project may include this in the offical plugin.
Best would be to offer a preferences String, that allows to add arbitrary annotations - maybe even a project specific setting. This would then be a generic solution and no "offical Spring support" which might have political acceptance issues.
See https://issues.jboss.org/browse/JBIDE-25748

I tried first solution explained by #Thies and it still works for org.jboss.tools.jsf_3.8.500.v20200930-0907.jar. Actually this is the only way I found to reach my Spring Beans (Having #Component etc...) from my xhtml files. I wish that he provided the .class files with support for all Spring annotations instead of just #Controller, but then I realized that I can do it for everybody. So I downloaded source code and recompiled those two classes to have #Component support and other Spring annotations. I also wanted to add detailed steps that are required to recompile the files, for the sake of inexperienced coders like me. :)
I tried everything on a clean installation of "Eclipse JEE 2020-09 R Win32 x86_64" and an empty Maven repository. UPPER CASE INFORMATIONS below are things that I have done wrong at the first time and then corrected, so I hope you do not repeat same time consuming mistakes. :)
Before starting the first solution explained by #Thies:
ADD ONLY 'JBoss Tools JSF' to the Eclipse. THIS STEP MUST BE THE FIRST STEP, because the compilation procedure (next steps) requires some common libraries installed on this step. You don't need to install any version of STS (Spring Tools Suite). DO NOT USE "Eclipse Marketplace", it will just confuse you.
Use 'Help' > 'Install New Software...' from the Eclipse main menu.
Add 'JBoss Tools' repository clicking on 'Add...' button. I named it 'JBoss Tools' and the location URL was http://download.jboss.org/jbosstools/photon/stable/updates/ for me. I found that URL after I clicked on 'Download' button at https://tools.jboss.org/downloads/jbosstools/, under 'Update Site' tab.
After the repository is selected on the drop-box, FIND AND CHECK ONLY THE 'JBoss Tools JSF' BOX (it is under 'JBoss WEB and Java EE Development' tree) and follow the instructions. Its dependencies will automatically be installed by Eclipse if necessary, so you don't need to check any other check-boxes, only 1 checked.
Eclipse will ask for permission to install 'Jboss Tools JSF' because Eclipse doesn't trust 'JBoss Tools' repository. Install anyway and restart the eclipse as requested at the end of the installation.
Download the latest , 'javaee' project source code from GitHub here. Download it as a zip archive (we will not use all of them), extract 'jbosstools-javaee-master' folder to your eclipse workspace BUT DO NOT IMPORT ALL OF THEM. It is not necessary and it takes a little long. Continue reading:
On Eclipse select 'File' > 'Import...'
On 'Import' window, BROWSE AND SELECT ONLY <your-eclipse-workspace>\jbosstools-javaee-master\jsf\plugins\org.jboss.tools.jsf\ folder. Then hit the 'Finish'" button. Wait for Eclipse to do its job, DO NOT TRY TO RUSH THINGS, just wait. :) It will download maven dependencies total of 20-25 MB.
Install anything if it asks for them (E.g. "tycho" plugins). Follow as Eclipse instructs. And install anyway even if Eclipse does not trust them. Restart Eclipse if it asks for it.
Wait if there are any ongoing action that are started automatically by Eclipse. Check the 'Progress' view of Eclipse to see if there are any ongoing actions.
Right click on the project ('org.jboss.tools.jsf' project) on 'Project Explorer' view of Eclipse. Then select 'Maven' -> 'Update Project' just to be sure everything is all right.
On Eclipse main menu find 'Project' -> 'Clean' to Open 'Clean' window. Ensure ONLY "org.jboss.tools.jsf" project is selected and 'Build only the selected projects' selection is active to save some time (assuming you have other projects in your workspace which aren't relevant to us). Then hit the 'Clean' button and wait for eclipse to clean an rebuild the project.
Find <your-eclipse-workspace>\jbosstools-javaee-master\jsf\plugins\org.jboss.tools.jsf\src\org\jboss\tools\jsf\jsf2\bean\model\impl\AbstractMemberDefinition.java file (find it on Eclipse project) and perform the changes proposed by #Thies as first solution on previous post. But this time make the changes with support for all Spring annotations. :) See his commented out code and update as necessary. It might be something like this:
Add the imports to the end of the other imports at top:
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMemberValuePair;
import org.jboss.tools.common.java.IAnnotationType;
Replace the below functions:
public boolean isAnnotationPresent(String annotationTypeName) {
//TW: added Spring annotations
boolean b = (getAnnotation(annotationTypeName) != null);
if (!b && JSF2Constants.MANAGED_BEAN_ANNOTATION_TYPE_NAME.equals(annotationTypeName)) {
b = (getAnnotation("org.springframework.stereotype.Controller") != null
|| getAnnotation("org.springframework.stereotype.Service") != null
|| getAnnotation("org.springframework.stereotype.Repository") != null
|| getAnnotation("org.springframework.stereotype.Component") != null);
}
return b;
}
public AnnotationDeclaration getManagedBeanAnnotation() {
AnnotationDeclaration ad = annotationsByType.get(JSF2Constants.MANAGED_BEAN_ANNOTATION_TYPE_NAME);
//TW: added Spring annotations
if (ad != null) return ad;
ad = annotationsByType.get("org.springframework.stereotype.Controller");
if (ad == null) ad = annotationsByType.get("org.springframework.stereotype.Component");
if (ad == null) ad = annotationsByType.get("org.springframework.stereotype.Service");
if (ad == null) ad = annotationsByType.get("org.springframework.stereotype.Repository");
if (ad != null) {
// create wrapper to map "value" (used by Spring) to "name" (which is used by #ManageBean)
ad = new AnnotationDeclaration() {
private AnnotationDeclaration wrapped;
AnnotationDeclaration init(AnnotationDeclaration wrappedAD) {
this.wrapped = wrappedAD;
return this;
}
#Override
public Object getMemberValue(String name) {
Object val = wrapped.getMemberValue(name);
if (val == null && "name".equals(name)) {
val = wrapped.getMemberValue(null);
}
return val;
}
#Override
public Object getMemberValue(String name, boolean resolve) {
Object result = null;
if (resolve) {
result = this.getMemberConstantValue(name);
}
if (result == null) {
result = this.getMemberValue(name);
}
return result;
}
#Override
public void setDeclaration(IJavaAnnotation annotation) {
wrapped.setDeclaration(annotation);
}
#Override
public IJavaAnnotation getDeclaration() {
return wrapped.getDeclaration();
}
#Override
public IResource getResource() {
return wrapped.getResource();
}
#Override
public IMemberValuePair[] getMemberValuePairs() {
return wrapped.getMemberValuePairs();
}
#Override
public Object getMemberConstantValue(String name) {
return wrapped.getMemberConstantValue(name);
}
#Override
public Object getMemberDefaultValue(String name) {
return wrapped.getMemberDefaultValue(name);
}
#Override
public IMember getParentMember() {
return wrapped.getParentMember();
}
#Override
public String getTypeName() {
return wrapped.getTypeName();
}
#Override
public IType getType() {
return wrapped.getType();
}
#Override
public int getLength() {
return wrapped.getLength();
}
#Override
public int getStartPosition() {
return wrapped.getStartPosition();
}
#Override
public IAnnotationType getAnnotation() {
return wrapped.getAnnotation();
}
#Override
public IAnnotation getJavaAnnotation() {
return wrapped.getJavaAnnotation();
}
#Override
public IMember getSourceMember() {
return wrapped.getSourceMember();
}
#Override
public IJavaElement getSourceElement() {
return wrapped.getSourceElement();
}
}.init(ad); // class
}
return ad;
}
Repeat Step-4. (On Eclipse main menu perform 'Project' > 'Clean' and wait for Eclipse to rebuild)
Find AbstractMemberDefinition.class and AbstractMemberDefinition$1.class files on <your-eclipse-workspace>\jbosstools-javaee-master\jsf\plugins\org.jboss.tools.jsf\target\classes\org\jboss\tools\jsf\jsf2\bean\model\impl folder.
Exit Eclipse and perform "Installation" steps from first solution of #Thies on previous post. I used "7-Zip" to open and update all ZIP and JAR archives, because it is free and easy to use.
Here are the AbstractMemberDefinition.class and AbstractMemberDefinition$1.class files I compiled to support 4 spring annotations; #Component, #Service, #Repository and #Controller. I hope they save some time for somebody one day.
I hope somebody will publish the result of https://issues.jboss.org/browse/JBIDE-25748 in the future and we will just laugh remembering these days and all the coding we have done because of our laziness. :)

Related

Logging with XQuery

I'm using XQuery 3.0 to transform an incoming message to fit my system.
The XQuery is called from an Apache Camel Route via the transform EIP.
Example:
transform().xquery("resource:classpath:xquery/myxquery.xquery",String.class)
While the transformation works without problems it would be nice, since it's partly very complex, to be able to log some informations directly during the transformation process.
So I wanted to ask if it is possible to log "into" logback directly from XQuery?
I already searched stackoverflow and of course https://www.w3.org/TR/xquery-30-use-cases/ and other sources, but I just couldn't find any information about how to log in Xquery.
My project structure is:
Spring-Boot 2 application
Apache-Camel as Routing framework
Logback as Logging framework
Update: For the integration of XQuery in the Apache-Camel Framework I use the org.apache.camel:camel-saxon-starter:2.22.2.
Update: Because the use of fn:trace was kind of ugly I searched further and now I use the extension mechanism from Saxon to provide different logging functions which can be accessed via xquery:
For more information see the documentation: http://www.saxonica.com/documentation/#!extensibility/integratedfunctions/ext-full-J
Here is what I did for logging (tested with Saxon-HE, Camel is not mandatory, I just use it by coincidence):
First step:
Extend the class net.sf.saxon.lib.ExtensionFunctionDefinition
public class XQueryInfoLogFunctionDefinition extends ExtensionFunctionDefinition{
private static final Logger log = LoggerFactory.getLogger(XQueryInfoLogFunctionDefinition.class);
private final XQueryInfoExtensionFunctionCall functionCall = new XQueryInfoExtensionFunctionCall();
private static final String PREFIX = "log";
#Override
public StructuredQName getFunctionQName() {
return new StructuredQName(PREFIX, "http://thehandofnod.com/saxon-extension", "info");
}
#Override
public SequenceType[] getArgumentTypes() {
return new SequenceType[] { SequenceType.SINGLE_STRING };
}
#Override
public SequenceType getResultType(SequenceType[] suppliedArgumentTypes) {
return SequenceType.VOID;
}
#Override
public ExtensionFunctionCall makeCallExpression() {
return functionCall;
}
}
Second step:
Implement the FunctionCall class
public class XQueryInfoExtensionFunctionCall extends ExtensionFunctionCall {
private static final Logger log = LoggerFactory.getLogger(XQueryInfoLogFunctionDefinition.class);
#Override
public Sequence call(XPathContext context, Sequence[] arguments) throws XPathException {
if (arguments != null && arguments.length > 0) {
log.info(((StringValue) arguments[0]).getStringValue());
} else
throw new IllegalArgumentException("We need a message");
return EmptySequence.getInstance();
}
}
Third step:
Configure the SaxonConfiguration and bind it into the camel context:
public static void main(String... args) throws Exception {
Main main = new Main();
Configuration saxonConfig = Configuration.newConfiguration();
saxonConfig.registerExtensionFunction(new XQueryInfoLogFunctionDefinition());
main.bind("saxonConfig", saxonConfig);
main.addRouteBuilder(new MyRouteBuilder());
main.run(args);
}
Fourth step:
Define the SaxonConfig in your XQueryEndpoint:
.to("xquery:test.xquery?configuration=#saxonConfig");
Fifth step:
Call it in your xquery:
declare namespace log="http://thehandofnod.com/saxon-extension";
log:info("Das ist ein INFO test")
Original post a.k.a How to overwrite the fn:trace Funktion:
Thanks to Martin Honnen I tried the fn:trace function. Problem was that by default it logs into the System.err Printstream and that's not what I wanted, because I wanted to combine the fn:trace function with the Logback Logging-Framework.
So I debugged the net.sf.saxon.functions.Trace methods and came to the following solution for my project setup.
Write a custom TraceListener which extends from net.sf.saxon.trace.XQueryTraceListener and implement the methods enter and leave in a way that the InstructionInfo with constructType == 2041 (for user-trace) is forwarded to the SLF4J-API. Example (for only logging the message):
#Override
public void enter(InstructionInfo info, XPathContext context) {
// no call to super to keep it simple.
String nachricht = (String) info.getProperty("label");
if (info.getConstructType() == 2041 && StringUtils.hasText(nachricht)) {
getLogger().info(nachricht);
}
}
#Override
public void leave(InstructionInfo info) {
// no call to super to keep it simple.
}
set the custom trace listener into your net.sf.saxon.Configuration Bean via setTraceListener
Call your xquery file from camel via the XQueryEndpoint because only there it is possible to overwrite the Configuration with an option: .to("xquery:/xquery/myxquery.xquery?configuration=#saxonConf"). Unfortunately the transform().xquery(...) uses it's own objects without the possibility to configure them.
call {fn:trace($element/text(),"Das ist ein Tracing Test")} in your xquery and see the message in your log.

Osgi ConfigurationAdmin delay in activating Component

I have a service that requires a configuration
#Component(service=InstrumenterService.class ,configurationPid = "InstrumenterService", configurationPolicy = ConfigurationPolicy.REQUIRE, scope = ServiceScope.PROTOTYPE)
public class InstrumenterService
This service is referenced inside another service :
#Component(service = SampleService.class, scope = ServiceScope.PROTOTYPE)
public class SampleService {
#Reference(cardinality = ReferenceCardinality.OPTIONAL, scope = ReferenceScope.PROTOTYPE_REQUIRED, policyOption = ReferencePolicyOption.GREEDY)
InstrumenterService coverageInstrumenter;
public boolean hasInstrumenter() {
if(coverageInstrumenter == null)
return false;
return true;
}
}
This SampleService is used inside a Main class hooked to the main osgi thread.
I'm using ComponentServiceObjects as I want to create on demand SampleServices.
#Component(immediate = true, property = "main.thread=true")
public class Main implements Runnable {
#Reference
ConfigurationAdmin cfgAdm;
#Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
private ComponentServiceObjects<SampleService> sampleServices;
public void run() {
if (cfgAdm != null) {
Configuration configuration;
try {
configuration = cfgAdm.getConfiguration("InstrumenterService", "?");
Hashtable<String, Object> props = new Hashtable<>();
props.put("some_prop", "some_value");
configuration.update(props);
} catch (IOException e1) {
e1.printStackTrace();
}
}
SampleService servicess = sampleServices.getService();
System.out.println(servicess.hasInstrumenter());
}
}
The problem I have is that the configuration set by the ConfigurationAdmin is not visible in the InstrumenterService unless I put a Thread.sleep(500); command after calling the configuration.update.
I'm not really confortable using a Thread.sleep command to ensure the configuration update is visible.
Is there an API to check that the configuration has been updated and is available to use ?
Thanks to Neil I was able to find a workable solution.
I used a ServiceTracker after the configuration was set to wait for the service:
BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext();
ServiceTracker serviceTracker = new ServiceTracker(bundleContext, InstrumenterService.class.getName(), null);
serviceTracker.open();
try {
serviceTracker.waitForService(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
serviceTracker.close();
The reason I needed ConfigurationAdmin in the first place is because there is an interface IInstrumenter which can be implemented by many different classes.
The name of this instrumenter is set in the ConfigurationAdmin and then further on in other services the required instrumeter service is fetch "automagically".
This way any number of instrumenter could be added to the application and only the name of the instrumeter needs to be known in order for it to be used.
I want to mention also that with OSGI we managed to split our monolith legacy application in more modules (~15) and they do not depend directly on each other but use an API layer.
Thanks again for the good job you are doing with OSGI.
As clarified in the comments, this code is not exactly realistic. In production code there is not normally a requirement to update a configuration record and then immediately obtain a service published by a component. This is because any such code makes too many assumptions about the effect of the configuration update.
A call to getServiceReference and getService returns only a snapshot of the service registry state at a particular instant. It is inherently unreliable to call getService expecting it to return a value.
In reality, we always use a pattern where we react to being notified of the existence of the service. This can be done in various ways, including ServiceListener and ServiceTracker, but the simplest is to write a component with a reference, e.g.:
#Component
public class MyComponent {
#Reference
SampleService service;
public void doSomething() {
println(service.hasInstrumenter());
}
}
This component has a mandatory reference to SampleService and will only be activated only when an instance of SampleService is available.

Cannot start bundle programmatically

Note: In my case, I'm using Apache Felix implementation if that's matters.
I have written bundle which I'm using as test. It's very simple "Hello World" bundle that do nothing more than print message to stdout when started/stopped:
public class Activator implements BundleActivator {
#Override
public void start(BundleContext context) throws Exception {
System.out.println("Hello, World.");
}
#Override
public void stop(BundleContext context) throws Exception {
System.out.println("Goodbye, World.");
}
}
There is also MANIFEST file which rather pointless to post since when I deploy above bundle through Apache Felix console from standard distribution (which can be downloaded here) bundle starts and print out message.
Next step I'm trying to do is deploy the very same bundle using programmatic approach. Unfortunately this is not working for me. My code looks as follow:
public static void main(String[] args) throws Exception {
FrameworkFactory frameworkFactory = getFrameworkFactory();
Framework framework = frameworkFactory.newFramework(null);
System.out.println("BundleID = " + framework.getBundleId());
System.out.println("State = " + getState(framework.getState()));
framework.init();
System.out.println("BundleID = " + framework.getBundleId());
System.out.println("State = " + getState(framework.getState()));
BundleContext bundleContext = framework.getBundleContext();
bundleContext.addBundleListener((event) -> {
System.out.println("Bundle Changed Event");
});
bundleContext.addFrameworkListener((event) -> {
System.out.println("Framework Event");
});
bundleContext.addServiceListener((event) -> {
System.out.println("Service Changed Event");
});
Bundle bundle = bundleContext.installBundle("file://<absolute-path-to-bundle-jar-same-as-above");
System.out.println("BundleID = " + bundle.getBundleId());
System.out.println("State = " + getState(bundle.getState()));
bundle.start();
System.out.println("BundleID = " + bundle.getBundleId());
System.out.println("State = " + getState(bundle.getState()));
}
private static FrameworkFactory getFrameworkFactory() throws IllegalStateException {
ServiceLoader<FrameworkFactory> loader = ServiceLoader.load(FrameworkFactory.class);
FrameworkFactory factory = null;
for (FrameworkFactory iterator : loader) {
if (factory != null) {
throw new IllegalStateException("Ambiguous SPI implementations.");
}
factory = iterator;
}
return factory;
}
private static String getState(int state) {
switch (state) {
case Bundle.UNINSTALLED:
return "UNINSTALLED";
case Bundle.INSTALLED:
return "INSTALLED";
case Bundle.RESOLVED:
return "RESOLVED";
case Bundle.STARTING:
return "STARTING";
case Bundle.STOPPING:
return "STOPPING";
case Bundle.ACTIVE:
return "ACTIVE";
default:
throw new IllegalStateException("Unknown state");
}
}
The output looks like follow:
BundleID = 0
State = INSTALLED
BundleID = 0
State = STARTING
Bundle Changed Event
BundleID = 1
State = INSTALLED
BundleID = 1
State = INSTALLED
So as far as I understand bundle got installed but last 4 lines indicate that bundle.start() got ignored for some reason.
Could you point out me what am I missing to make this work?
After hour of debugging and reading through javadoc more carefully this is happening because framework was only initialized instead of being started. To make example work you have to simply add framework.start() after framework.init() (or just call framwork.start() which calls framework.init() if found it necessary).
I'm leaving this information as there are few confusing things:
Official documentation to Apache Felix have information about embedding framework into host application. Unfortunately there is only example that use Apache Felix custom mechanisms that make it not portable to other implementations. What is confusing is warning note which if you want to create portable solution you should use init() and getBundleContext(). Whole note cited bellow:
WARNING The felix.systembundle.activators configuration property is specific to the Felix framework implementation. If you want your code to work with other framework implementations, you should call init() on the framework instance and use getBundleContext() directly. Otherwise, the approach would be very similar.
JavaDoc for parameterless version of init() method do not mention about initialization is not same as starting the framework, although init(FrameworkListener...) have such information.
This Framework will not actually be started until start is called.

Spring repository without database but external api

We are building an API for our service and we would like to leverage Spring Data Rest as much as possible.
This API and the new model underneath will substitute a legacy API (and it's old model) that we still need to support.
Our idea is to build an "adapter" web app that replicates the structure of the old api and serve the old model using some internal transformations.
Also the old api is using Spring Data Rest, so here the idea:
build a repository implementation that instead of querying a database will query our brand new API, retrieve the new model, apply some transformations, and return the old model.
Unfortunately, even if I'm annotating the repository implementation with the #Repository annotation, Spring is not exposing the repository in the API.
I'm not sure if this is actually something possible to do or is just a matter of me not implementing some core functionalities.
What I would like to avoid is reimplement all spring data rest methods manually in a controller.
Here my Repository class
// Method are not implemented, this is just the backbone
#Repository
public class SampleRespositoryImpl implements ReadOnlyRepository<OldSample, String> {
NewApiClient client;
public SampleRespositoryImpl(NewApiClient client) {
this.client = client;
}
#Override
public OldSample findOne(String accession) {
NewSample newSample = client.fetch(accession)
OldSample oldSample = //apply transformation to newSample
return oldSample;
}
#Override
public boolean exists(String accession) {
return client.fetch(accession) != null;
}
#Override
public Iterable<OldSample> findAll() {
return new ArrayList<>();
}
#Override
public Iterable<OldSample> findAll(Iterable<String> var1) {
return new ArrayList<>();
}
#Override
public long count() {
return 0;
}
#Override
public Iterable<OldSample> findAll(Sort var1) {
return new ArrayList<>();
}
#Override
public Page<OldSample> findAll(Pageable var1) {
List<OldSample> OldSampleList = new ArrayList<>();
Page<OldSample> page = new PageImpl<>(OldSampleList);
return page;
}
}
Here what I would like to get back when I hit the api root (http://localhost:8080/)
{
"_links": {
"samples": {
"href": "http://localhost:8080/samples{?page,size,sort}
}
}
}
Someone else linked me to another answer in StackOverflow available here as possible duplication.
Reading through that answer, I decided that is too much effort to follow this path for our needs, so I'm more oriented to create a custom controller to expose necessary methods.
This solution was reported by Kevin as answer to Implementing methods of Spring Data repository and exposing them through REST

Get resteasy servlet context without annotation params

Quick project explanation: We have a built application based on JSF2 + Spring with Dynamic data sources. The data reference control is made with a spring-config:
<bean id="dataSource" class="com.xxxx.xxxx.CustomerRoutingDataSource">
....
and a class (referenced above):
public class CustomerRoutingDataSource extends AbstractRoutingDataSource {
#Override
protected Object determineCurrentLookupKey() {
return CustomerContextHolder.getCustomerType();
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
return null;
}
}
the CustomerContextHolder called above is as follows:
public class CustomerContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setCustomerType(String customerType) {
contextHolder.set(customerType);
}
public static String getCustomerType() {
String manager = (String)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("dataBaseManager");
if (manager != null) {
contextHolder.set(manager);
FacesContext.getCurrentInstance().getExternalContext().getSessionMap().put("dataBaseManager", null);
} else {
String base = (String)FacesContext.getCurrentInstance().getExternalContext().getSessionMap().get("currentDatabBase");
if (base != null)
contextHolder.set(base);
}
return (String) contextHolder.get();
}
public static void clearCustomerType() {
contextHolder.remove();
}
}
The problem is that the last guy is calling FacesContext.getCurrentInstance() to get the servlet context. Just to explain, it uses the session Attribute dataBaseManager to tell which base it should use.
For the actual solution it was working fine, but with the implementation of a RESTEASY web service, when we make a get request the FacesContext.getCurrentInstance() is obviously returning null and crashing.
I searched a lot and could not find a way of getting the servlet-context from outside of the #GET params. I would like to know if is there any way of getting it, or if there is another solution for my dynamic datasource problem.
Thanks!
Like magic and probably not much people know.
I searched deep into the Resteasy documentation, and found a part of springmvc plugin that comes with the resteasy jars, that has a class called RequestUtil.class.
With that I was able to use the method getRequest() without the "#Context HttpServletRequest req" param.
Using that I was able to set the desired database on the request attributes, and from another thread (called by spring) get it and load the stuff from the right place!
I'm using it for a week now and it works like a charm. Only thing that I needed to do is change the determineLookupKey() above to this:
#Override
protected String determineCurrentLookupKey() {
if (FacesContext.getCurrentInstance() == null) {
//RESTEASY
HttpServletRequest hsr = RequestUtil.getRequest();
String lookUpKey = (String) hsr.getAttribute("dataBaseManager");
return lookUpKey;
}else{
//JSF
return CustomerContextHolder.getCustomerType();
}
}
Hope this helps other people!
Thiago

Resources