I'd like to include a resource file (e.g. some xml config file) in my bundle and make it visible to all other bundles in the container. Is it possible without using the Fragment-Host manifest header? I'd like this resource file to always be visible in the classpath of all bundles running alongside my bundle, even those that do not exist yet, but will potentially be added in future.
EDIT:
To clarify - that resource must be available passively, i.e. the other bundles should be able to find it in their classpath, and not by refering to any special API or service of my bundle.
Some more background - my environment is a bit messy but I have no control over it and cannot change its existing bundles. The only way I can modify it is by adding my own bundles. That environment includes several copies of the ch.qos.logback.classic bundle. When logback starts up, it looks for specific XML config files in the classpath. If it doesn't find any of them, then its default behaviour is to print everything to stdout with debug level. This environment was previously used to host a GUI application so it didn't matter that much before, but now I am trying to adapt it so I can use some of its functionality in headless mode. So now it becomes important to me to be able to configure it in such a way that only warning and errors are printed to the console.
In general, no you cannot do this. Class-space isolation is at the heart of OSGi, but you want to put a resource in the class loader of one bundle and make it visible to all other bundles. That's not OSGi, it's the global application classpath.
The only thing you can do to add to the internal classpath of a specific bundle is to write a fragment which attaches to that bundle. A fragment can attach to multiple host bundles, but only if those hosts have the same symbolic name, i.e. because they are different versions of the same bundle. See OSGi R6 Core Specification, section 3.14.
You did however state that the bundles you want to attach are all copies of ch.qos.logback.classic. If that means they all have that exact symbolic name then perhaps a fragment will work after all.
You can not change the classpath of other bundles this way.
What you can do is retrieve the classloader of your bundle from your bundleContext. You can give this classloader to another bundle to retrieve your resource.
ClassLoader cl = context.getBundle().adapt(BundleWiring.class).getClassLoader();
Another option is to give the other bundle the URL of the resource.
As long as the resource is on the classpath, any bundle can access the resource if it can get hold of the class loader of the bundle that contains the resource.
For example:
ClassLoader classLoaderOfBundleWithResource = ...
classLoaderOfBundleWithResource.getResourceAsStream("org/example/resource.xml");
From a maintenance and API point of view, I would not recommend exposing a resource that way. Java types are much better suited therefore. Instead, let the resource bundle export a class that gives clients access to the contents of the resource.
For example:
public class XmlDocumentProvider {
public InputStream openDocument() {
return getClass().getResourceAsStream("resource.xml");
}
}
Assuming that both the resource.xml and the XmlDocumentProvider reside in the same package, openDocument will return the resource content just like in the first example.
Related
I've an desktop application that implements Serializable class, but now I'm translating it to an web app with servlets, I use Netbeans for this work,I have the following code:
InputStream input = ClassLoader.getSystemResourceAsStream(file_input);
Where file_input is an bytecode fyle from an object serialized before, I don't know where I should put this file because in the desktop application I put it in the same dir where I had my classes.
(I have the file, I don't need to create it).
First of all, don't use ClassLoader#getSystemResourceAsStream() in a Java EE web application, ever. Instead, use ClassLoader#getResourceAsStream(). I would also put big question marks around using ClassLoader#getSystemResourceAsStream() in a Java SE desktop application, for sure if it's intended to be distributable, but that aside.
The ClassLoader ultimately loads resources from the classpath. So all you need to do is to make sure that the file is placed in one of the paths which are by default covered by the webapp's runtime classpath, or to add the new path to the file to the webapp's runtime classpath through a server specific configuration setting, such as shared.loader property of Tomcat's /conf/catalina.properties.
One of the default paths covered by the webapp's runtime classpath is the /WEB-INF/classes folder of the WAR. From IDE project's perspective, just drop the file in the root folder of the "Java Source" (src) folder, there where you have all your Java packages and classes. The IDE will take care that it ultimately ends up in /WEB-INF/classes of the built WAR file.
I by the way still assume that you are not creating the file from inside the webapp, as you explicitly told. That wouldn't work. If you actually need to have write access as well, you're going to need an absolute disk file system path instead. You can always make it configureable by providing it as a VM argument or environment variable, for example.
This is for Struts 1.x (I'm using 1.3.10).
I've noticed that Struts is unable to pick up resource bundles in the ApplicationResources.properties file if it is not placed somewhere in the default classpath (e.g., com.abc.SomePackage).
For instance, if I put the ApplicationResources.properties file in a custom folder /WEB-INF/strutsResources and configure the struts-config.xml thus:
<message-resources parameter="/WEB-INF/strutsResources/ApplicationResources"/>
I've read that the resources need to be on the classpath so I've also tried adding the /WEB-INF/strutsResources folder to the classpath. It still does not pick up the resource keys.
I've double-checked that the strutsResources folder is actually deployed to the server (I'm using Glassfish v3), so the file is there, it's just not being parsed.
P.S.
If you're wondering why I'm trying to do this, I just wanted to organize my code a little better ("better," IMO). Since the ApplicationResources.properties file is not really a class, I wanted to place it in a resources folder by itself.
I've checked that placing the ApplicationResources file in a package in the src directory works just fine.
Ultimately, the answer is yes. You can play some interesting games by configuring a custom className and/or factory and get messages however you want (including from a database) and so on. This allows you to customize whatever you want*.
I agree the resources aren't a class, but putting them on the classpath is a common practice, and allows resources to be loaded as a resource, e.g., from inside a jar. I'm sympathetic, but I'd leave it as-is.
*Like reversing all the text; a fun prank to play on your co-workers and QA department.
Its best leave it on the classpath.
It's stadard practise to include properties files on the classpath, especially if you're planning on packaging it up in your WAR/EAR. You're keeping it under WEB-INF so you gain no benefit from moving it off the classpath, and you'll just confuse other developers who have to work on the project and you've had to put a hack in to make this work.
If you want to keep your files external to your deployable WAR/EAR then that's a valid reason for not using the classpath. Typically this will require some configuration as part of your deployment to specify where the file is to reside.
For example specify the location using
a JVM argument (e.g. -Dprops.file=/config/myapp.properites)
lookup from a JNDI resource
use a PropertiesFactoryBean if you're using the Spring framework (I
use Spring's ApplicationContext with Struts 1 MVC)
read properties from a database writing your own
ApplicationPropertiesDAO class that initialises itself durnig your
applications bootstrap process (e.g. Spring application contact,
Servlet in web.xml, Listener in web.xml, etc)
I am new to Eclipse plugin development, and I am trying to develop a plugin where I am required to load a class which is selected in the Navigator.
Can you please instruct me how can I load a class or create a classloader from eclipse plugin, to load a class in the eclipse workbench which is using my plugin.
Thanks in advance.
Regards
Gillani
You need to create a URLClassLoader. You may want it to be parented or not, depending on your security concerns. If parented by an OSGi bundle class loader, then the user will have access to all of the classes in that particular bundle and this may be a back door to the user getting runtime access to the entire Eclipse platform (and access the plugin registry and the OSGi services, etc). The user can also call System.exit(). This may not be a problem on a single user system, but it is something to think about, especially if the user may be downloading unverified scripts from the internet.
That being said, you need to do something like this:
Instantiate a URLClassLoader and add the URLs of the directories containing the class files you will want to load.
if you want the classes to have access to the Eclipse runtime, then set the parent loader to be
((BundleHost) Activator.getBundleContext().getBundle()).getLoaderProxy().getBundleLoader()
Add all of the directories corresponding to URLs where the user can load classes from. But, you must include all dependencies in order to load the classes.
This should be enough to load the classes you require.
I'm happily using the Maven bundle-plugin to create OSGi manifest headers for my modules. However, when there are configuration files that pull in classes which aren't referenced directly in the code, the plugin can't tell which packages it's going to need.
One example is a bundle with domain models that constitute a Persistence Unit for JPA. The driver class is part of the PU configuration and either set in an XML file or at runtime when the EntityManager is instantiated. I have to manually add an Import-Package header for the driver class that I want to load, or I get CNF errors.
Another example is a Struts war, where the web.xml pulls in the Struts dispatcher that's otherwise not found anywhere in the code and has to be manually added to the headers.
How can I avoid this?
I tried adding the required packages as dependencies with a provided scope, but that didn't help.
In the plug-in section of the bnd configuration you can specify plug-ins to analyze these files and contribute to the import-package header. For spring it looks like this:
<_plugin>aQute.lib.spring.SpringComponent</_plugin>
I am not sure, what descriptors are supported on top of spring. Just take a look at the source (it's in the Apache Felix SVN) and see for yourself. In the worst case you have to write your own plug-in, but at least it is possible! Also peter kriens site about the bnd explains the usage and some internals.
Other then that I am not aware of any simple solution.
I currently have an existing plugin, which references a class from a required plugin. I have replaced this code with a reference to a class which is part of my fragment.
I am facing two issues.
If I import my fragment as a jar file, I am not able to see the changes I have made as the plugin running as an eclipse application results in a ClassNotFoundException
To overcome this, I link an additional source (of fragment) to the existing plugin project. However, the link uses an absolute path, and makes it unfit for deployment.
I want to be able to package the plugin with the code modification and be able to "depend" on my fragment code. Is there a way I can add my fragment as a dependency?
For example:
Plugin Project I am changing : org.eclipse.*.editor
it depends on org.eclipse.*.edit
I have a fragment mydomain.*.edit which has org.eclipse.*.edit as host plugin
I want org.eclipse.*.editor to pick up mydomain.*.edit
instead of org.eclipse.*.edit
ps: I have also tried packaging the jar file for the mydomain.*.edit in the plugins directory and try and pick it up from there, it doesnt show up on the list when I click add required plugins on the dependency tab on the plugin.xml file of the org.eclipse.*.editor
Please let me know if I am not clear enough, I will try and rephrase it.
Thanks in advance!
If I understand correctly what you want to do, I don't think that it's possible. You will have to try some other way.
Plugins have dependencies on other plugins. Fragments don't exist as separate runtime entities, but only as extensions of a plugin. So your plugin can only refer to the 'editor' plugin.
Classes provided by a fragment can't (and shouldn't) be accessed directly. They can be returned by the original plugin (A) if they are implementing an executable extension provided by plugin A.
If you refer to the fragment's code from another plugin (B), the classes will be loaded by plugin B's classloader and be different from the ones that are loaded by plugin A.
What is the purpose of your fragment? Do you want to get access to internal code in plugin A? Do you want to extend an eclipse editor?
If you want to extend functionality that the original plugin is not exposing as extensible, I think the only way is to write a plugin, extend the editor class from the original plugin, register it alongside the original one and use it instead.
[Edit] Maybe this link will explain better: Eclipse FAQ
Hope this helps,
Vlad
Thanks Vlad,
Your explanation was very helpful. Unlike the extension based architecture that is truly intended for fragments, I had to modify a certain component in the editor that was not exposed as part of the extension. This modification referred to an external project I created as an fragment but could have been a normal java project packaged a jar file that I could place in the classpath of the editor.
I was able to resolve the dependency issues by placing the jar file in class path, however when I export the plugins and related plugins as jar files and place it in the dropin directory, it does not install correctly. (Nor does placing the jar files in the plugins directory)
The eclipse editor that I am trying to modify uses the EMF project. I have kept the EMF project in the workspace inorder to resolve dependencies of the editor. However when I replace the EMF jar files bundled with eclipse with the one in the workspace, the files that I want to edit are not correctly recognized.
Is there another way of doing this?