Cannot start bundle programmatically - osgi

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.

Related

StreamsException when running #QuarkusTest with kafka streams in quarkus development mode

Quarkus 2.15.3,
Problem
When running my test case via gradle build or IDE (IntelliJ), everything is fine, test OK.
When starting application in Quarkus Dev Mode, test fails with following exception:
Caused by: org.apache.kafka.streams.errors.StreamsException: Unable to initialize state, this can happen if multiple instances of Kafka Streams are running in the same state directory (current state directory is [C:\Users\username\AppData\Local\Temp\kafka-streams\my-service]
at org.apache.kafka.streams.processor.internals.StateDirectory.initializeProcessId(StateDirectory.java:191)
TopologyProducer, builds a simple topology, creating only a global state store
#ApplicationScoped
public class TopologyProducer {
#ConfigProperty(name = "sometopic")
String SOME_TOPIC;
#Produces
public Topology buildTopology() {
final var builder = new StreamsBuilder();
var keySerDe = new Serdes.StringSerde();
var valueSerDe = new ObjectMapperSerde<>(MyValue.class);
builder.globalTable(SOME_TOPIC, Consumed.with(keySerDe, valueSerDe), Materialized.as(MyStateStoreService.STORE_NAME));
return builder.build();
}
}
MyStateStoreService, reads a value from the global state store by key
#ApplicationScoped
public class MyStateStoreService {
public static final String STORE_NAME = "MyStore";
#Inject
KafkaStreams streams;
public MyValue valueById(String id) {
// Taken this from official documentation but tried with try-catch and streams.start() in catch-block too. Same result.
while (true) {
try {
return (MyValue) streams.store(StoreQueryParameters
.fromNameAndType(STORE_NAME, QueryableStoreTypes.keyValueStore())).get(id);
} catch (InvalidStateStoreException e) {
// Ignore, store not ready yet
}
}
}
}
MyStateStoreServiceTest, should test, that MyStateStoreService returns the newest version of a value for a specific key
#QuarkusTest
#QuarkusTestResource(KafkaCompanionResource.class)
public class MyStateStoreServiceTest {
#InjectKafkaCompanion
KafkaCompanion companion;
#ConfigProperty(name = "sometopic")
String SOME_TOPIC;
#Inject
MyStateStoreService myStateStoreService;
#BeforeEach
public void setUp() {
companion.registerSerde(String.class, new Serdes.StringSerde());
companion.registerSerde(MyValue.class, new ObjectMapperSerde<>(MyValue.class));
}
#Test
public void valueUpdatesCorrectly() {
// Given
var key= "valueUpdatesCorrectly";
var value = new MyValue("dummyValue");
var valueUpdated= new MyValue("dummyValueUpdated");
// When
companion
.produce(String.class, MyValue.class)
.fromRecords(
new ProducerRecord<>(SOME_TOPIC, key, value),
new ProducerRecord<>(SOME_TOPIC, key, valueUpdated))
.awaitCompletion();
// Then
assertEquals(valueUpdated, myStateStoreService.valueById(key));
}
}
By now, in my application there was no problem in using Dev Mode, #QuarkusTestResource, KafkaCompanion and a Topology without state stores together. Other tests, using this approach (taken from Testing using a Kafka broker), run fine.
As soon as this global state store is added to my topology, I get above exception ONLY during continuous tests in dev mode (for readability reasons I removed the code for the rest of my topology from TopologyProducer)
Is there something wrong in my test setup, misunderstanding of dev mode and kafka companion from my site, or something else I missed?

OSGi DS Prototype Reference not released

I've create a simple vaadin portlet in a Liferay 7/DXP or osgi 6 context and I noticed that my References do not get garbage collected if I use osgi declarative services with a prototype scope, but they do if I use serviceObjects. Why?
Note: I've updated this question and put an even more simple example at the end.
My main component is a prototype component which has a prototype reference to an object. If I use the osgi declarative services to declare my dependency (the HelloPresenter in the following listing), then my dependency won't be released and stays in the heap forever:
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.UI;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
import org.osgi.service.component.annotations.ReferenceScope;
import org.osgi.service.component.annotations.ServiceScope;
/**
* Created by marcel
*/
#Component(
property = {
"com.liferay.portlet.display-category=VaadinHelloMvp",
"javax.portlet.display-name=VaadinHelloMvp",
"javax.portlet.name=VaadinHelloMvp",
"com.vaadin.osgi.liferay.portlet-ui=true"
},
service = UI.class,
scope = ServiceScope.PROTOTYPE
)
public class VaadinHelloMvpPortlet extends UI {
#Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
private HelloPresenter helloPresenter;
#Override
protected void init(VaadinRequest request) {
this.setContent(helloPresenter.getViewComponent());
}
}
So I've tried get my service instance for my HelloPresenter programmatically, which this works fine:
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.UI;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.ServiceObjects;
import org.osgi.framework.ServiceReference;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ServiceScope;
/**
* Created by marcel
*/
#Component(
property = {
"com.liferay.portlet.display-category=VaadinHelloMvp",
"javax.portlet.display-name=VaadinHelloMvp",
"javax.portlet.name=VaadinHelloMvp",
"com.vaadin.osgi.liferay.portlet-ui=true"
},
service = UI.class,
scope = ServiceScope.PROTOTYPE
)
public class VaadinHelloMvpPortlet extends UI {
private HelloPresenter helloPresenter;
#Override
protected void init(VaadinRequest request) {
Bundle bundle = FrameworkUtil.getBundle(HelloPresenter.class);
ServiceReference<HelloPresenter> serviceReference = bundle.getBundleContext().getServiceReference(HelloPresenter.class);
ServiceObjects<HelloPresenter> serviceObjects = bundle.getBundleContext().getServiceObjects(serviceReference);
helloPresenter = serviceObjects.getService();
this.addDetachListener(event -> serviceObjects.ungetService(helloPresenter));
helloPresenter.init();
this.setContent(helloPresenter.getViewComponent());
}
}
So I wonder why my HelloPresenter won't be released by the osgi framework in the first scenario, but it does in the second?
My portlet (UI) object is also created with
serviceObjects.getService();
and released with
serviceObjects.ungetService(uiObject);
and I tried other scenarios where I set another prototype reference in my HelloPresenter, which will also produce a reference which won't be released and garbage collected. So my experience was that, whenever you create a service object which contains a prototype reference, the reference won't get released and stucks in the jvm heap, after releasing the service object
So I got the idea that either I am doing something wrong or missed a param which makes my prototype reference never getting released OR there is something wrong with mixing osgi declarative service and serviceObjects ...
Do you know how I can make my first example work? I want to use the annotations and also be sure that they become garbage collected after closing my portlet ui.
UPDATE
I've created an even more example with a singleton component to execute a gogo shell command and a prototype object which also contains a prototype reference:
#Component(
service = GogoShellService.class,
scope = ServiceScope.SINGLETON,
immediate = true,
property =
{
"osgi.command.scope=test",
"osgi.command.function=atest",
}
)
public class GogoShellService {
public String atest() {
Bundle bundle = FrameworkUtil.getBundle(APrototypeComponent.class);
ServiceReference<APrototypeComponent> serviceReference = bundle.getBundleContext().getServiceReference(APrototypeComponent.class);
ServiceObjects<APrototypeComponent> serviceObjects = bundle.getBundleContext().getServiceObjects(serviceReference);
APrototypeComponent service = serviceObjects.getService();
String s = "Hello From: " + service.sayHello();
serviceObjects.ungetService(service);
return s;
}
}
#Component(scope = ServiceScope.PROTOTYPE, service = APrototypeComponent.class, servicefactory = true)
public class APrototypeComponent {
#Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
AProInAProComp aProInAProComp;
public String sayHello() {
String hello = "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ") ";
if (aProInAProComp != null) {
hello += aProInAProComp.sayHello();
}
return hello;
}
}
#Component(scope = ServiceScope.PROTOTYPE, service = AProInAProComp.class)
public class AProInAProComp {
public String sayHello() {
return "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ")";
}
}
Every time I execute the command (GogoShellService#atest) a new prototype instance is created and should also be destroyed afterwards, but I still can see this object in my heap and running the garbage collection doesn't clean this up...
osgi debug output is the following:
[org_apache_felix_scr:94] getService {de.foo.bar.bax.gogo.GogoShellService}={osgi.command.function=atest, component.name=de.foo.bar.bax.gogo.GogoShellService, component.id=2944, osgi.command.scope=test, service.id=7827, service.bundleid=51, service.scope=bundle}: stack of references: []
APrototypeComponent(2942)] ServiceFactory.getService()
AProInAProComp(2941)] ServiceFactory.getService()
AProInAProComp(2941)] This thread collected dependencies
AProInAProComp(2941)] getService (ServiceFactory) dependencies collected.
AProInAProComp(2941)] Querying state active
AProInAProComp(2941)] Changed state from active to active
APrototypeComponent(2942)] This thread collected dependencies
APrototypeComponent(2942)] getService (ServiceFactory) dependencies collected.
APrototypeComponent(2942)] Querying state satisfied
APrototypeComponent(2942)] For dependency aProInAProComp, optional: false; to bind: [[MultiplePrototypeRefPair: ref: [{de.foo.bar.bax.checkosgi.AProInAProComp}={component.name=de.foo.bar.bax.checkosgi.AProInAProComp, component.id=2941, service.id=7823, service.bundleid=51, service.scope=prototype}] has service: [true]]]
APrototypeComponent(2942)] Changed state from satisfied to active
APrototypeComponent(2942)] ServiceFactory.ungetService()
APrototypeComponent(2942)] DependencyManager: aProInAProComp close component unbinding from org.apache.felix.scr.impl.manager.ComponentContextImpl#3927bc1d at tracking count 1 refpairs: [[MultiplePrototypeRefPair: ref: [{de.foo.bar.bax.checkosgi.AProInAProComp}={component.name=de.foo.bar.bax.checkosgi.AProInAProComp, component.id=2941, service.id=7823, service.bundleid=51, service.scope=prototype}] has service: [true]]]
APrototypeComponent(2942)] Querying state active
APrototypeComponent(2942)] Changed state from active to satisfied
I don't see why my prototype instances cannot get garbage collected...
Update for new readers
As of Apache Felix SCR 2.1.14 this problem should be fixed and the perfectly valid code from the original question will no longer result in erroneous behaviour.
Original Answer
Firstly, thank you for working to create a simple example to demonstrate your question!
You are absolutely right that SCR should release all of your component's references after it is deactivated. For a ReferenceScope.PROTOTYPE_REQUIRED scoped reference this should result in the service instance being released and tidied up.
Sadly it seems as though this feature of SCR hasn't been working for a while. I raised https://issues.apache.org/jira/browse/FELIX-5974 on your behalf, so it should be fixed soon, but for now the "easy" workaround is to take control of the lifecycle yourself.
#Component(
service = GogoShellService.class,
scope = ServiceScope.SINGLETON,
immediate = true,
property =
{
"osgi.command.scope=test",
"osgi.command.function=atest",
}
)
public class GogoShellService {
public String atest() {
Bundle bundle = FrameworkUtil.getBundle(APrototypeComponent.class);
ServiceReference<APrototypeComponent> serviceReference = bundle.getBundleContext().getServiceReference(APrototypeComponent.class);
ServiceObjects<APrototypeComponent> serviceObjects = bundle.getBundleContext().getServiceObjects(serviceReference);
APrototypeComponent service = serviceObjects.getService();
String s = "Hello From: " + service.sayHello();
serviceObjects.ungetService(service);
return s;
}
}
#Component(scope = ServiceScope.PROTOTYPE, service = APrototypeComponent.class, servicefactory = true)
public class APrototypeComponent {
#Reference(scope = ReferenceScope.PROTOTYPE_REQUIRED)
ComponentServiceObjects<AProInAProComp> cso;
AProInAProComp aProInAProComp
#Activate
void start() {
aProInAProComp = cso.getService();
}
#Deactivate
void stop() {
cso.ungetService(aProInAProComp);
aProInAProComp = null;
}
public String sayHello() {
String hello = "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ") ";
if (aProInAProComp != null) {
hello += aProInAProComp.sayHello();
}
return hello;
}
}
#Component(scope = ServiceScope.PROTOTYPE, service = AProInAProComp.class)
public class AProInAProComp {
public String sayHello() {
return "Hello From " + this.getClass().getSimpleName() + "(" + this.toString() + ")";
}
}

EL autocomplete / code assist with Eclipse and Spring Beans

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

Spring boot 1.3.x MultipartFile.transferTo null after migration from 1.2.x

I am facing error MultipartFile error for one day after having upgrade from Spring Boot 1.2.7 to 1.3.1.
What I notice is that the default is now Jetty 9.2 and no more Tomcat 8. Everything was fine until I tried to write an uploaded file using MultipartFile.transferTo(File file) method..
MultipartFile.transferTo() method is calling an implementation of javax.servlet.http.Part Which is implemented this way for tomcat 8
#Override
public void write(String fileName) throws IOException {
File file = new File(fileName);
if (!file.isAbsolute()) {
file = new File(location, fileName);
}
try {
fileItem.write(file);
} catch (Exception e) {
throw new IOException(e);
}
}
and this way for jetty 9.2
public void write(String fileName) throws IOException
{
if (_file == null)
{
_temporary = false;
//part data is only in the ByteArrayOutputStream and never been written to disk
_file = new File (_tmpDir, fileName);
BufferedOutputStream bos = null;
try
{
bos = new BufferedOutputStream(new FileOutputStream(_file));
_bout.writeTo(bos);
bos.flush();
}
finally
{
if (bos != null)
bos.close();
_bout = null;
}
}
else
{
//the part data is already written to a temporary file, just rename it
_temporary = false;
File f = new File(_tmpDir, fileName);
if (_file.renameTo(f))
_file = f;
}
}
What's wrong with the Jetty implementation is that is waiting for file name File.getName() and not the absolute path name File.getPath() which is provided by the call of StandardMultipartHttpServletRequest.transferTo(File file)
#Override
public void transferTo(File dest) throws IOException, IllegalStateException {
this.part.write(dest.getPath());
}
Is this a bug ? Note that this occurs since I have upgraded from spring boot 1.2.7 to 1.3.1. The default was Tomcat and now it is Jetty...
Per the javadoc for javax.servlet.http.Part.write(String filename) the filename parameter is ...
The file is created relative to the location as specified in the
MultipartConfig
In the code you referenced in Jetty 9.2, namely this ...
jetty-9.2.14.v20151106 - MultiPartInputStreamParser.write(String fileName)
You'll see that there's 2 possible code paths it takes, the first is the "in memory" path, and the second is "file on disk" approach.
In both cases, when you specify a filename to Part.write(String) that name is relative to your MultiPartConfig.location (a configuration of which you haven't detailed in your question).
The implementation of MultiPartInputStreamParser has a _tmpDir which is configured from the webapp's MultiPartConfig.location.
If you want this to behave properly, would highly recommend you define a MultiPartConfig.location that is appropriate for your application, instead of relying on the container to pick one.
The Tomcat approach of allowing absolute filenames in Part.write(String) is actually not allowed in the servlet spec (mainly as its a security issue that can be used to cause havoc on a system)
Ok, at the moment if you want to get rid of this error you can switch back to Tomcat instead of Jetty.
Put tomcat into your dependencies:
compile('org.springframework.boot:spring-boot-starter-tomcat')
And declare tomcat as container:
#Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}

Embedded Jetty Error The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved

I'm using Embedded Jetty server to run my tests and Maven for build.
Following is the code used to start Jetty before tests.
System.out.println("Initializing Jetty Server...");
jettyServer = new Server(0);
WebAppContext webapp = new WebAppContext("src/main/webapp", "/testApp");
jettyServer.addHandler(webapp);
jettyServer.start();
int actualPort = jettyServer.getConnectors()[0].getLocalPort();
String baseUrl = "http://localhost:" + actualPort + "/testApp";
All the tests passes if I run it with 'Run as Junit Test'. No problems here.
But if I run it as Maven Test or Maven Install The tests fails withe following cause
Caused by: com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException: 500 /WEB-INF/pages/test1.jsp(3,62) PWC6188: The absolute uri: http://java.sun.com/jsp/jstl/core cannot be resolved in either web.xml or the jar files deployed with this application
I have the JSTL dependancy added in POM.xml. Please help to resolve this..
You didn't initialize and setup the environment in a way that is suitable for JSP use.
It requires a bunch of extra work.
You'll need to manipulate classloaders, setup some initializers, declare the javac implementation behavior, and even declare the jsp servlet handling. (missing any one of these and you'll be subject to the environment that you executed under, which is different in your 3 examples)
For a complete example maven project see https://github.com/jetty-project/embedded-jetty-jsp
public class Main
{
// Resource path pointing to where the WEBROOT is
private static final String WEBROOT_INDEX = "/webroot/";
public static void main(String[] args) throws Exception
{
int port = 8080;
LoggingUtil.config();
Log.setLog(new JavaUtilLog());
Main main = new Main(port);
main.start();
main.waitForInterrupt();
}
private static final Logger LOG = Logger.getLogger(Main.class.getName());
private int port;
private Server server;
private URI serverURI;
public Main(int port)
{
this.port = port;
}
public URI getServerURI()
{
return serverURI;
}
public void start() throws Exception
{
server = new Server();
ServerConnector connector = new ServerConnector(server);
connector.setPort(port);
server.addConnector(connector);
URL indexUri = this.getClass().getResource(WEBROOT_INDEX);
if (indexUri == null)
{
throw new FileNotFoundException("Unable to find resource " + WEBROOT_INDEX);
}
// Points to wherever /webroot/ (the resource) is
URI baseUri = indexUri.toURI();
// Establish Scratch directory for the servlet context (used by JSP compilation)
File tempDir = new File(System.getProperty("java.io.tmpdir"));
File scratchDir = new File(tempDir.toString(),"embedded-jetty-jsp");
if (!scratchDir.exists())
{
if (!scratchDir.mkdirs())
{
throw new IOException("Unable to create scratch directory: " + scratchDir);
}
}
// Set JSP to use Standard JavaC always
System.setProperty("org.apache.jasper.compiler.disablejsr199","false");
// Setup the basic application "context" for this application at "/"
// This is also known as the handler tree (in jetty speak)
WebAppContext context = new WebAppContext();
context.setContextPath("/");
context.setAttribute("javax.servlet.context.tempdir",scratchDir);
context.setResourceBase(baseUri.toASCIIString());
context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
server.setHandler(context);
// Add Application Servlets
context.addServlet(DateServlet.class,"/date/");
//Ensure the jsp engine is initialized correctly
JettyJasperInitializer sci = new JettyJasperInitializer();
ServletContainerInitializersStarter sciStarter = new ServletContainerInitializersStarter(context);
ContainerInitializer initializer = new ContainerInitializer(sci, null);
List<ContainerInitializer> initializers = new ArrayList<ContainerInitializer>();
initializers.add(initializer);
context.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
context.addBean(sciStarter, true);
// Set Classloader of Context to be sane (needed for JSTL)
// JSP requires a non-System classloader, this simply wraps the
// embedded System classloader in a way that makes it suitable
// for JSP to use
ClassLoader jspClassLoader = new URLClassLoader(new URL[0], this.getClass().getClassLoader());
context.setClassLoader(jspClassLoader);
// Add JSP Servlet (must be named "jsp")
ServletHolder holderJsp = new ServletHolder("jsp",JspServlet.class);
holderJsp.setInitOrder(0);
holderJsp.setInitParameter("logVerbosityLevel","DEBUG");
holderJsp.setInitParameter("fork","false");
holderJsp.setInitParameter("xpoweredBy","false");
holderJsp.setInitParameter("compilerTargetVM","1.7");
holderJsp.setInitParameter("compilerSourceVM","1.7");
holderJsp.setInitParameter("keepgenerated","true");
context.addServlet(holderJsp,"*.jsp");
//context.addServlet(holderJsp,"*.jspf");
//context.addServlet(holderJsp,"*.jspx");
// Add Example of mapping jsp to path spec
ServletHolder holderAltMapping = new ServletHolder("foo.jsp", JspServlet.class);
holderAltMapping.setForcedPath("/test/foo/foo.jsp");
context.addServlet(holderAltMapping,"/test/foo/");
// Add Default Servlet (must be named "default")
ServletHolder holderDefault = new ServletHolder("default",DefaultServlet.class);
LOG.info("Base URI: " + baseUri);
holderDefault.setInitParameter("resourceBase",baseUri.toASCIIString());
holderDefault.setInitParameter("dirAllowed","true");
context.addServlet(holderDefault,"/");
// Start Server
server.start();
// Show server state
if (LOG.isLoggable(Level.FINE))
{
LOG.fine(server.dump());
}
// Establish the Server URI
String scheme = "http";
for (ConnectionFactory connectFactory : connector.getConnectionFactories())
{
if (connectFactory.getProtocol().equals("SSL-http"))
{
scheme = "https";
}
}
String host = connector.getHost();
if (host == null)
{
host = "localhost";
}
int port = connector.getLocalPort();
serverURI = new URI(String.format("%s://%s:%d/",scheme,host,port));
LOG.info("Server URI: " + serverURI);
}
public void stop() throws Exception
{
server.stop();
}
/**
* Cause server to keep running until it receives a Interrupt.
* <p>
* Interrupt Signal, or SIGINT (Unix Signal), is typically seen as a result of a kill -TERM {pid} or Ctrl+C
*/
public void waitForInterrupt() throws InterruptedException
{
server.join();
}
}
In the following repo: https://github.com/ericminio/learning-jetty
You can find:
The JstlTest that demoes Jetty serving jsp containing a c:forEach tag
The pom needed to remove the error message about resolving http://java.sun.com/jsp/jstl/core
Hope it helps

Resources