I'm writing a Java servlet that needs to read some site-specific
configuration data; I would like it to be easily accessible/modifiable
by the sysadmins at deployment time. There is no sensible default,
so the data has to be provided by the site admin.
It consists of a few string key/value pairs (think Properties).
It would only be read once (at initialization time).
I'm aware of this SO question
and the ServletContext.getInitParameter() mechanism, but as far as
my understanding goes, they require the data to be bundled in the
servlet package (either as a properties file, or specified in the
web.xml), which makes it inconvenient to upgrade the servlet code.
Is there any "standard" interface for a servlet to get this kind of
key/value configuration data? It would be ok if the programming
interface is the same everywhere, but the actual way of setting the
configuration data depends on the actual servlet container being used.
I'm looking preferably at portable solutions, but I'd be content with
something that only works in Tomcat and Jetty.
The recommended way to configure an application server for a web application is per JNDI.
Every application server (including Jetty and Tomcat) allows you to configure JNDI parameters.
For Jetty you can add the following to your jetty.xml to add the JNDI parameter param.file:
<!-- JNDI java:comp/env -->
<New id="param.file" class="org.mortbay.jetty.plus.naming.EnvEntry">
<Arg>param.file</Arg>
<Arg type="java.lang.String"><SystemProperty name="jetty.home" default="."/>etc/config.properties</Arg>
<Arg type="boolean">true</Arg>
</New>
Then in your servlet you can read the JNDI parameter:
import javax.naming.InitialContext;
import javax.naming.NamingException;
...
public Object readJndi(String paramName) {
Object jndiValue = null;
try {
final InitialContext ic = new InitialContext();
jndiValue = ic.lookup("java:comp/env/" + paramName);
} catch (NamingException e) {
// handle exception
}
return jndiValue;
}
public String getConfigPath() {
return (String) readJndi("param.file");
}
The way to set JNDI values differs for other application servers but the code to read the configuration is always the same.
The Servlet init parameters are the right (and standardized) way of defining properties which can be configured by the administrator. Many of the application servers provide a GUI backend where the parameters can be configured.
For an example for Tomcat, see Defining Tomcat servlet context parameters
Configure the external location of the properties - either via a jvm argument (when starting the servlet container), or in the web.xml
in the external location use config.properties and read it with java.util.Properties
You may take Preferences or hack with user.home, user.dir, etc. But for a few key/value keep things simple.
Write a small Singleton to wrap around Properties and load them from a fix & absolute location
public class LocalConfig extends Properties {
public static LocalConfig $ = new LocalConfig();
private LocalConfig() throws IOException {
load(new File("/etc/myconfig.properties"));
}
}
Related
Not sure if this question was previously asked, but I cannot seem to find the answer.
Where does one store the properties when loaded in a webapp.
I have a web application which has settings to allow system administrators via a user interface to change settings in the app.
For example the app allows only selected user groups to be able to go a certain page.
I wanted to allow system admins to go in and set the user groups that can go to the above mentioned page, and then change it at a later date.
The issue I'm facing is that once loaded the properties file, where do I store the data rather than continuously loading the properties file each time a user goes into the page.
I'm probably not getting the full concept of how properties are used so any guidance would be greatly appreciated.
Just make sure I can read the user groups in, can change the user groups without reloading the class/app and allow it to be thread safe and quick without two different threads having two different properties because we are using a load balanced environment. With a content share which is where the properties files are stored and accessed (not having any issues with this so not looking for help with where to store the properties file).
Any help greatly appreciated.
EDIT 1
The application runs on a clustered environment which means that other application servers could potentially have different values due to multiple ServletContexts.
Register ServletContextListener to load Init parameters and properties at server start-up.
Load properties at single time and make it visible to other classes statically or you can store it in application context as well to access it from anywhere such as JSP and Servlet.
Note: Make the properties file location configurable in web.xml rather than hard-coding it in Java class. You can retrieve the properties file location as system environment variable as well.
Sample code:
public class AppServletContextListener implements ServletContextListener {
private static Properties properties;
#Override
public void contextInitialized(ServletContextEvent servletContextEvent) {
String cfgfile = servletContextEvent.getServletContext().getInitParameter("config_file");
properties.load(new FileInputStream(cfgfile));
// store it in application scope as well
servletContextEvent.getServletContext().setAttribute("prop",properties);
}
public static Properties getProperties(){
return properties;
}
}
web.xml:
<listener>
<listener-class>com.x.y.z.AppServletContextListener</listener-class>
</listener>
<context-param>
<param-name>config_file</param-name>
<param-value>config_file_location</param-value>
</context-param>
Please have a look at my another post that is asked in the same context:
Retrieve Init parameters outside servlet
EDIT
If you are changing the properties at run-time then don't use Servlet context according to the ServletContext javadoc:
In the case of a web application marked "distributed" in its deployment descriptor, there will be one context instance for each virtual machine. In this situation, the context cannot be used as a location to share global information (because the information won't be truly global). Use an external resource like a database instead.
The Servlet specification also states in "SRV.4.4.1 Context Attributes in a Distributed Container":
Context attributes are local to the JVM in which they were created. This prevents ServletContext attributes from being a shared memory store in a distributed container. When information needs to be shared between servlets running in a distributed environment, the information should be placed into a session (See Chapter SRV.7, “Sessions”), stored in a database, or set in an Enterprise JavaBeansTM component.
In that case you can try with some third party cache that works in distributed environment as well as mentioned below:
EHCache
Infinispan
OR store all the properties in the database.
The Servlet container offers the concept of Contexts. I find it helpful to consider a Context as a useful box for storing things in, and operates like a Map.
There are a number of different Contexts available to a Java Webapp, and they differ in scope (that is, how long the data held in the context lasts for, and where it can be accessed from). There is the Page Context, the Session Context and the Servlet Context
The Page Context has the narrowest scope, and is only lasts as long as a single page takes to process.
The Session Context has a greater scope, and lasts as long as single user session, i.e. multiple requests from a browser. It is useful if your webapp requires authentication - information about the authenticated user will be stored in the Session Context.
The Servlet Context is effectively global and is always available to the whole application. This is where I would recommend storing configuration properties which effect the functioning of the application.
In a Servlet, you may access the Servlet Context like this:
ServletContext context = request.getSession().getServletContext();
You can store something in the context like this:
context.setAttribute("key", object);
Where key is a String - the name of the attribute.
You may retrieve it again like this:
object = context.getAttribute("key");
Which returns an Object. You may cast it to whatever type it really is. If you want to, you can store a Properties object in it:
Properties props = //... get the properties from file
context.setAttribute("props", props);
And then retrieve them:
Properties props = (Properties) context.getAttribute("props");
Or you can store the individual properties as separate attributes in the context.
All contexts are accessed the same way.
You could go with the classic singleton pattern, where you have a single ApplicationProperties class which holds globally valid values for your application backed by a property file so no part of your application has to care about how to store the properties. Pseudo code:
public class ApplicationProperties {
private static final String PATH = "app.properties";
private static final ApplicationProperties INSTANCE = new ApplicationProperties();
private String userGroup;
private ApplicationProperties() {
// Load properties from PATH and populate fields.
this.userGroup = ...
}
public static ApplicationProperties getInstance() {
return INSTANCE;
}
public String getUserGroup() {
return this.userGroup;
}
public String setUserGroup(String userGroup) {
// Save to property file to persist.
this.userGroup = userGroup;
}
}
You just have to synchronize access to the fields that no two threads overwrite properties and create race conditions.
What is the best approach for creating services that load a property set at runtime (bean is passed "xyz" and loads xyz.properties)? These properties files need to be able to be dropped into a folder outside the classpath before a command is entered to start the service (edit: this could happen at any time while the program is running).
I already have a system to do this that we've been using for over a year, but I'm migrating to spring to make the code more modular (customize services more easily through DI) and easier to maintain. My current method of creating an environment and then passing it with "this" to the dependencies just seems upside down from an IoC standpoint.
Is there a way to use a PropertyPlaceholderConfigurer without hardcoding the name of the property file? Maybe just a reference to a variable I pass into the constructor of the service that its dependencies can load? So far, it looks like I will have to create a service and inject its dependencies without any config and then call a separate load method for each to pass in the properties, but that just seems like I'm not really using spring.
USE CASE: The app will pool client connections to various servers and will forward requests from other applications to these servers. New profiles must be able to be added by non-programmers without taking down or restarting the app. The profiles will include basic things like host, port, and login info, but also more complex things like whether to use tcp/http, ssl/https (which will determine which client type to use), and timeouts and pool min/max/etc (which will need default values).
I tried with PropertyPlaceholderConfigurer and frankly, I couldn't wrap my head around it, somehow. It's easy enough to use when you use the existing options but I couldn't extend the framework.
So my approach was much more simple:
Create an annotation #InjectConfig which takes a config key as parameter.
In your beans/services, annotate fields or public setters with this annotation.
Write a BeanPostProcessor which takes options from a "config provider" and injects them into the fields / setters.
Now all you need is a config provider. Inject that into the post processor and you're done.
Note: I prefer annotating setters because that means you can easily configure your services from tests (just call the setters) without having to come up with smart names for 238576 config files.
EDIT If you have many configs, then a config factory might be a better choice:
Create a key to describe a config bundle (I usually use an enum or a new type here to prevent typos)
Put this key into the service when you create it (manually or via Spring)
Write a config factory that can return Properties or a Map for a config key.
Inject this factory into your service
In the init code of your service, use the key to lookup your config via the factory.
Using this approach, you can have a dummy factory that always returns the same thing in tests and a more complex factory for production.
The real factory can then be configured via spring so it knows where to look for configuration files. One approach is to register a java.io.File per config key. Now your concerns (configuring a service and loading configs) are completely separated.
PropertyPlaceholderConfigurer reads and initialize files on application context initialization and only once. So most probably you cannot configure it at runtime.
But you can have variables. For example, for my case I have default properties and user specific properties. So PropertyPlaceholderConfigurer loads properties from classpath first and after that is trying to find additional properties at defined location (user home folder). I user's property file exists so configurer loads it and override properties.
Here is my example:
<bean id="config" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="ignoreResourceNotFound" value="true"/> <!-- do not throw exception if file not found -->
<property name="locations">
<list>
<value>classpath:server.properties</value>
<value>file:${user.home}/user.properties</value>
</list>
</property>
</bean>
I'm not sure that this answer is what you're exactly need. But I'm trying to guess what is your actual task. So if you need re-read properties runtime each time you access them you have to do it manually like you did before because spring application context helps you configure your application initial configuration.
It seems like the best approach may be to use a ServiceManager that contains the main ApplicationContext and then have each Service initialize its own FileSystemXmlApplicationContext with the main context as the parent like so:
public class ServiceManager {
ApplicationContext appContext;
String APP_HOME = System.getProperty("user.home") + File.separator;
public void init() {
//set main spring context
appContext = new AnnotationConfigApplicationContext(AppConfig.class);
}
public void start(String serviceName) throws Exception {
ApplicationContext serviceContext = new FileSystemXmlApplicationContext(
new String[]{APP_HOME + serviceName + ".xml"}, //path to child ctx
appContext); //reference to parent ctx to build hierarchy
Service service = (Service) serviceContext.getBean("service");
service.start();
}
}
The ApplicationContext is a bit heavy to be duplicating, but memory is pretty cheap these days and this provides total separation of concerns. I still have shared logging and an event system managed by the parent context, and each service is now simplified in its own config. I built a proof of concept using two services, and it seems to work fine so far. I'll add another comment once I finish the other services and finish testing.
reference:
http://techo-ecco.com/blog/spring-application-context-hierarchy-and-contextsingletonbeanfactorylocator/
Helo masters, I have to create a JNDI Datasource dynamically, I tried to do it with a listener called SetupApplicationListener. Here is the beginning of WEB-LIB/web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee">
<display-name>pri-web</display-name>
<!-- Listeners -->
<listener>
<listener-class>org.apache.myfaces.webapp.StartupServletContextListener</listener-class>
</listener>
<listener>
<listener-class>myapp.SetupApplicationListener</listener-class>
</listener>
The code of the listener:
public class SetupApplicationListener implements ServletContextListener {
public static Log LOG = null;
public void contextInitialized(ServletContextEvent ctx){
try {
createOracleDataSource();
.....
}
}
private void createOracleDataSource() throws SQLException, NamingException {
OracleDataSource ds = new OracleDataSource();
ds.setDriverType(...);
ds.setServerName(...);
ds.setPortNumber(...);
ds.setDatabaseName(...);
ds.setUser(...);
ds.setPassword(...);
new InitialContext().bind("java:comp/env/jdbc/myDS", ds);
}
.....
}
And there is the error:
[ERROR] 29/01/2013 09:44:50,517 (SetupApplicationListener.java:86) -> Error
javax.naming.NamingException: Context is read only
at org.apache.naming.NamingContext.checkWritable(NamingContext.java:903)
at org.apache.naming.NamingContext.bind(NamingContext.java:831)
at org.apache.naming.NamingContext.bind(NamingContext.java:171)
at org.apache.naming.NamingContext.bind(NamingContext.java:187)
at org.apache.naming.SelectorContext.bind(SelectorContext.java:186)
at javax.naming.InitialContext.bind(InitialContext.java:359)
at myapp.SetupApplicationListener.createOracleDataSource(SetupApplicationListener.java:102)
Can I set the read-only properties of the Context to "true"? Thanks! :)
Tomcat 6.0
Oracle 11g
jdk1.5
EDIT: Don't need to be dynamically, i have to define a jndi datasource internally I can't modify the server files because it is a shared server. It must be jndi because other modules use it in that way, thanks.
If you need to create a datasource dynamically is there really any need for a JNDI lookup? JNDI is designed to make the connection external to the application, while in your scenario its tightly coupled to the application due to a legitimate requirement. Why not just use a JDBC connection?
You need to create a ServletContextListener and there you can make the InitialContext writable - it's not the way it should be done, but if you really need it, this is one way you can do it.
This also works with Java Melody!
protected void makeJNDIContextWritable(ServletContextEvent sce) {
try {
Class<?> contextAccessControllerClass = sce.getClass().getClassLoader().loadClass("org.apache.naming.ContextAccessController");
Field readOnlyContextsField = contextAccessControllerClass.getDeclaredField("readOnlyContexts");
readOnlyContextsField.setAccessible(true);
Hashtable readOnlyContexts = (Hashtable) readOnlyContextsField.get(null);
String context = null;
for (Object key : readOnlyContexts.keySet()) {
String keyString = key + "";
if (keyString.endsWith(sce.getServletContext().getContextPath())) {
context = keyString;
}
}
readOnlyContexts.remove(context);
} catch (Exception ex) {
ex.printStackTrace();
}
}
I haven't got this problem before since I usually defined JNDI in application server(tomcat, weblogic and etc). Just like what Kevin said, this is exactly what JNDI was designed for; separating datasource config from your source code and retrieving JNDI resources through lookup and inject;
Back to your question, I think tomcat has every strict rules on modifying JNDI at runtime. In another word, you cannot re-bind or remove jndi from Context. If you go through the tomcat specification you will probably see some thing about jndi lookup but no re-bind.
From section EE.5.3.4 of the EE 6 platform specification (JSR 316):
The container must ensure that the application component instances
have only read access to their naming context. The container must
throw the javax.naming.OperationNotSupportedException from all the
methods of the javax.naming.Context interface that modify the
environment naming context and its subcontexts.
Note that "their naming context" in this section is referring to java:comp.
I solved this problem when found that I was closing environmentContext object
For example:
Context context=new InitialContext();
Context environmentContext=(Context) context.lookup("java:comp/env");
And my code was:
environmentContext.close();
After removing close function from environmentContext problem was solded for me;
I also had this problem, but being new to Tomee, I didn't know that there is a simple solution. When I deployed my web app to the webapps folder, the app worked fine, but when I deployed it to a service folder, I got the same abort. The problem was that the folder name did not match the war name (minus the .war). Once I fixed that, the app worked fine. Make sure the war name, folder name and service name are identical. This problem produces several different errors, including Context is read only and Error merging Java EE JNDI entries.
I solved this issue by setting useNaming="false" in my context.xml.
From the documentation:
useNaming : Set to true (the default) to have Catalina enable a JNDI InitialContext for this web application that is compatible with Java2 Enterprise Edition (J2EE) platform conventions.
Here's my Java class
import endpoint.NewSessionRemote;
import javax.naming.Context;
import javax.naming.InitialContext;
public class HelloClient {
public static void main(String[] args) {
try {
Context ctx = new InitialContext();
NewSessionRemote hello = (NewSessionRemote) ctx.lookup("endpoint.NewSessionRemote");
System.out.println(hello.stringChange(4));
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
When I run this class I'm getting an exception.
javax.naming.NameNotFoundException: endpoint.NewSessionRemote not found
at com.sun.enterprise.naming.TransientContext.doLookup(TransientContext.java:216)
at com.sun.enterprise.naming.TransientContext.lookup(TransientContext.java:188)
at com.sun.enterprise.naming.SerialContextProviderImpl.lookup(SerialContextProviderImpl.java:74)
at com.sun.enterprise.naming.RemoteSerialContextProviderImpl.lookup(RemoteSerialContextProviderImpl.java:129)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.sun.corba.ee.impl.presentation.rmi.ReflectiveTie._invoke(ReflectiveTie.java:154)
at com.sun.corba.ee.impl.protocol.CorbaServerRequestDispatcherImpl.dispatchToServant(CorbaServerRequestDispatcherImpl.java:687)
at com.sun.corba.ee.impl.protocol.CorbaServerRequestDispatcherImpl.dispatch(CorbaServerRequestDispatcherImpl.java:227)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleRequestRequest(CorbaMessageMediatorImpl.java:1846)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleRequest(CorbaMessageMediatorImpl.java:1706)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleInput(CorbaMessageMediatorImpl.java:1088)
at com.sun.corba.ee.impl.protocol.giopmsgheaders.RequestMessage_1_2.callback(RequestMessage_1_2.java:223)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.handleRequest(CorbaMessageMediatorImpl.java:806)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.dispatch(CorbaMessageMediatorImpl.java:563)
at com.sun.corba.ee.impl.protocol.CorbaMessageMediatorImpl.doWork(CorbaMessageMediatorImpl.java:2567)
at com.sun.corba.ee.impl.orbutil.threadpool.ThreadPoolImpl$WorkerThread.run(ThreadPoolImpl.java:555)
java.lang.NullPointerException
All the other enterprise bean classes are written according to the EJB 3.0 standard.
Your valuable contribution is expected.
Solution
The exception was
javax.naming.NameNotFoundException: endpoint.NewSessionRemote not found
It occurs because the JNDI name that was given by the application side didn't match the servser's (Glassfish) actual JNDI name, so I did was check the JNDI tree in Glassish through its admin console (vendor specific) and I did notice that the JNDI for the NewSessionRemote interface (which is the business interface of the session bean NewSessionBean) is different from the name which I have given in the application side. So how did this happen then suddenly something came in to my mind that's the ejb-jar.xml there is another name JNDI name assigned to the same NewSessionRemote using tag. So I simply remove it and redeploy EJB module. That's it.
Looks like you have no RMI registry (i.e. active server) you are lookingUp() against.
You supplied no Context.INITIAL_CONTEXT_FACTORY variable, so the lookup should be a valid URL, which it is not.
Hence, you should put something like this on your env (on the iCtx):
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.rmi.registry.RegistryContextFactory");
I suggest you read the the simple examples over at http://java.sun.com/j2se/1.5.0/docs/guide/jndi/jndi-rmi.html
When using JNDI, you're using an API that requires a specific configuration underlying it in order to connect to the server (see the Javadoc for details on what that configuration is). For example, java.naming.factory.initial is the property which indicates which implementation of JNDI you want to use.
Now, when running code inside a JavaEE server, this configuration is available implicitly, and all you need to do is what you have done in your code - instantiate InitialContext, and perform a lookup. However, when running outside the server, this implicit configuration is not present, and so you need to configure your InitialContext explicitly.
Your sample code uses a main() method, which suggests that you're running outside the container. The config you need will depend on your specific application server, you'll need to look up that documentation to see what config to supply.
The actual question is: Is there a way to get XmlWebApplicationContext to load resources using paths relative to the context location? For clarity's sake, let's say "context location" is the location of the first file specified via setConfigLocation() method.
Detailed explanation is below:
I'm using Spring MVC in web tier and Spring IOC in mid tier. Appropriate contexts are defined hierarchically as described in Spring Documentation: web stuff is defined in my-servlet.xml and services et al are defined in services.xml that's loaded via ContextLoaderListener. Mid tier can be deployed either together with web tier (e.g. the whole thing runs within ServletContainer) or separately (in which case services.xml is replaced by remote-services.xml defining remote stubs). The whole setup works perfectly except for the following problem:
I have certain resources (additional XML files, what have you) located in the same folder as services.xml that need to be accessible by said services. Those resources are specified as dependencies in services.xml using relative paths. When mid tier is deployed standalone that works fine, but not when it's deployed within servlet container. In the latter case mid tier context gets instantiated as XmlWebApplicationContext which loads all resources based of servlet context root meaning I have to prefix everything with /WEB-INF/ which I'd really like to avoid. Using PropertyPlaceholderConfigurer presents a similar problem as well.
I know I can work around this somewhat by having resources load from classpath, but that's not ideal either - for standalone deployment it means I need to add configuration folder to classpath and for web deployment it means everything has to be copied under WEB-INF/classes.
Any ideas?
I've ended up extending Spring's XmlWebApplicationContext to allow relative resource paths. This does what I want, that is allows me to use the same context.xml file no matter whether it's deployed as part of web app or standalone.
For anyone interested source is available below. It's published using SOV (Stack Overflow Voting) license :-) which means you're free to do whatever you want with it as long as you upvote this answer :-)
import java.io.IOException;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;
/**
* Extends Spring's default web application context to allow relative
* resource paths. Resources without explicitly specified URL protocol
* and / or leading slash are loaded relative to the first location
* from getConfigLocations().
*/
public class SpringApplicationContext extends XmlWebApplicationContext {
#Override
protected Resource getResourceByPath(String path) {
path = StringUtils.cleanPath(path);
if (path.startsWith("/") || (path.indexOf(':')>0)) {
return super.getResourceByPath(path);
}
try {
return super.getResourceByPath(getConfigLocations()[0])
.createRelative(path);
} catch (IOException E) {
// failed to create relative resource - default to standard implementation
return super.getResourceByPath(path);
}
} // getResourceByPath()
}
I agree that is rather annoying. I get around this by doing what you suggest, which is putting my spring config on the classpath, so even though I still use fully-qualified imports, they work under any environment.
I'm not sure why your classpath config needs to be that complex, though. The files can just under your java source folder alongside the java files, so they get handled the same.
Strange. Your solution does not work for me. Here is mine:
package dmp.springframework.web.context;
import java.io.IOException;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.util.StringUtils;
import org.springframework.web.context.support.XmlWebApplicationContext;
public class RelativeResourceXmlWebApplicationContext extends XmlWebApplicationContext {
#Override
protected Resource getResourceByPath(String path) {
path = StringUtils.cleanPath(path);
if (path.startsWith("/") || (path.contains(":"))) {
return super.getResourceByPath(path);
}
try {
String newFilename = super.getResourceByPath(getConfigLocations()[0]).getFile().getParentFile().getAbsolutePath();
newFilename = newFilename + "/" + path;
return new FileSystemResource(newFilename);
} catch (IOException E) {
// failed to create relative resource - default to standard implementation
return super.getResourceByPath(path);
}
} // getResourceByPath()
}