How do I add aliases to a Servlet Context in java?
Asked Answered
P

4

2

I have a servlet running under Tomcat. I need to serve some files, I guess we can call them semi-static (which change occasionally ... they are updated by another part of the app) from an external (to the WEB-APP) directory. I have managed to do this by adding the following to my context.xml in the META-INF directory

<Context aliases="/working_dir=c:/apache_tomcat_working_dir" ></Context>

This works fine, in my HTML I refer to the file as

<img src="/myWebbApp/working_dir/fixpermin_zoom.png">

and in my web.xml inside WEB-INF I let the default server handle png files as follows

<!-- use default for static serving of png's, js and css, also ico -->
<servlet-mapping>
    <servlet-name>default</servlet-name>
    <url-pattern>*.png</url-pattern>
</servlet-mapping>

So this works fine. But I want to set the external directory from inside java code, not by editing the context.xml file.

Now in the init() method of the servlet I can get the ServletContext.

    ServletContext sc =  getServletContext();

If I examine this variable sc in the debugger, I can see the alias string several levels deep, see the attached image. How can I get at this alias string programatically? I have checked the ServletContext docs, but i can't find it very helpful. Any help much appreciated.

debug view of serveletcontext
(source: choicecomp.com)

Projector answered 3/10, 2012 at 19:19 Comment(0)
S
2

As you can see in your debugger, your context is Tomcat's Context Object org.apache.catalina.core.StandardContext

You can try following steps in Tomcat 6 and below:

    StandardEngine engine = (StandardEngine) ServerFactory.getServer().findService("Catalina").getContainer();
    StandardContext context = (StandardContext) engine.findChild(engine.getDefaultHost()).findChild(getServletContext().getContextPath());
    Mapper mapper = context.getMapper();

Now you can add Host alias using addHostAlias(String HostName, String alias) method of the Mapper class.

    mapper.addHostAlias(engine.getDefaultHost(), "myAlias");

Here is the code snippet for Tomcat 7:

    MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
    ObjectName name = new ObjectName("Catalina", "type", "Server");
    Server server = (Server) mBeanServer.getAttribute(name, "managedResource");
    StandardEngine engine = (StandardEngine) server.findService("Catalina").getContainer();
    StandardContext context = (StandardContext) engine.findChild(engine.getDefaultHost()).findChild(getServletContext().getContextPath());
    Mapper mapper = context.getMapper();
    mapper.addHostAlias(engine.getDefaultHost(), "myAlias");

If there is no host in the mapper, please try below:

    MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
    ObjectName name = new ObjectName("Catalina", "type", "Server");
    Server server = (Server) mBeanServer.getAttribute(name, "managedResource");
    StandardEngine engine = (StandardEngine) server.findService("Catalina").getContainer();
    StandardContext context = (StandardContext) engine.findChild(engine.getDefaultHost()).findChild(getServletContext().getContextPath());
    Mapper mapper = context.getMapper();
    //just a clean up step(remove the host)
    mapper.removeHost(engine.getDefaultHost());
    //add the host back with all required aliases 
    mapper.addHost(engine.getDefaultHost(), new String[]{"myAlias"}, engine.getDefaultHost());

Hope this helps!

Salomo answered 3/10, 2012 at 19:44 Comment(4)
thanks for the answer, but this does not work for me on Tomcat 7. I have checked in the debugger and the aliases section stays the same after the addHostAlias call. BTW where did you find this info? Is there a good tutorial on the Tomcat server internals, or are you browsing Tomcats source javadoc or what?Projector
This is working code. Please try the last section in my updated answer.Salomo
Thanks again, but this still does not work. Where are you calling this snippet of code? Inside the servlet Init() function? How do you know that this works? can you see the change in the context aliases member in the debugger? Does the server find the files you are serving up? I have put dummy names into my aliases in my context.xml file, then the alias I try adding by your method:a) dont show up in the debugger, and b) the servelet fails to serve the static files from my working dirProjector
Yes. I am using Tamcat 7.0.30 and calling this code snippet on doGet method of my test servlet. In my debugger, before this code snippet is executed, I can see there is no alias assigned(in my case, there was no host either). After the execution, I can see that host is added with given aliases.Salomo
S
2

I found another method StandardContext.setAliases. Find below the full working code snippet for Tomcat 7.0.30.

        MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
        ObjectName name = new ObjectName("Catalina", "type", "Server");
        Server server = (Server) mBeanServer.getAttribute(name, "managedResource");
        StandardEngine engine = (StandardEngine) server.findService("Catalina").getContainer();
        StandardContext context = (StandardContext) engine.findChild(engine.getDefaultHost()).findChild(getServletContext().getContextPath());
        context.setAliases("myAlias");
        //infact aliases should be proper e.g. below 
        //context.setAliases("/aliasPath1=docBase1,/aliasPath2=docBase2");
        Mapper mapper = context.getMapper();
        mapper.removeHost(engine.getDefaultHost());
        mapper.addHost(engine.getDefaultHost(), new String[]{"myAlias"}, engine.getDefaultHost());
        mapper.addHostAlias(engine.getDefaultHost(), "myAlias");
        //infact aliases should be proper e.g. below 
        //mapper.addHostAlias(engine.getDefaultHost(), "/aliasPath1=docBase1,/aliasPath2=docBase2");

Please find my debugger screenshots below:

Before the code snippet execution: enter image description here enter image description here

After the code snippet execution: enter image description here enter image description here

Hope this is more helpful.

Salomo answered 4/10, 2012 at 15:1 Comment(2)
Hi Yogendra, thanks that function context.SetAliases(... does definitely change the aliases that I see in the debugger. However my servlet is still not serving up the static files from my aliased directory. I have the same alias string that previously worked for me in the context.xml file. I presume that the lines of code referring to mapper are now no longer necessary? Where are you finding the info or docs for this? Thanks againProjector
Primarily I referred Javadocs. This is what it says: "Set the current alias configuration. The list of aliases should be of the form "/aliasPath1=docBase1,/aliasPath2=docBase2" where aliasPathN must include a leading '/' and docBaseN must be an absolute path to either a .war file or a directory."Salomo
M
2

Here is my working code to dynamically set Tomcat7 context alias depending on different operating systems. Sure you can improve on it

public class ContextListener implements ServletContextListener {
@Override
public void contextInitialized(ServletContextEvent sce) {
    ServletContext context = sce.getServletContext();

    // tomcat 7.x
    try {
        MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
        ObjectName name = new ObjectName("Catalina", "type", "Server");
        Object server = mBeanServer.getAttribute(name, "managedResource");

        Object service = server.getClass().getMethod("findService", String.class).invoke(server, "Catalina");  //StandardService[Catalina]
        Object connectors = service.getClass().getMethod("findConnectors").invoke(service);
        Object engine = service.getClass().getMethod("getContainer").invoke(service);  //StandardEngine[Catalina]
        Object host = Array.get(engine.getClass().getMethod("findChildren").invoke(engine), 0);  //StandardHost[Catalina]
        Object stdContext = Array.get(host.getClass().getMethod("findChildren").invoke(host), 0);  //StandardContext[Catalina]
        Object mapper = stdContext.getClass().getMethod("getMapper").invoke(stdContext);
        //just a clean up step(remove the host)
        Field f1 = mapper.getClass().getDeclaredField("context");
        f1.setAccessible(true);
        Object ct = f1.get(mapper);

        Field f2 = ct.getClass().getDeclaredField("resources");
        f2.setAccessible(true);
        Object rs = f2.get(ct);

        Field f3 = rs.getClass().getDeclaredField("dirContext");
        f3.setAccessible(true);
        Object dc = f3.get(rs);

        mapper.getClass().getMethod("removeHost",String.class).invoke(mapper, host.getClass().getMethod("getName").invoke(host));
        //add the host back with all required aliases
        switch (OsCheck.getOperatingSystemType()) {
            case Windows:
                dc.getClass().getMethod("setAliases",String.class).invoke(dc,"/img/avatars=" + winAvatarAlias);
                break;
            default:
                dc.getClass().getMethod("setAliases",String.class).invoke(dc,"/img/avatars=" + linuxAvatarAlias);
                break;
        }
        String ports = "";
        for (Object o :(Object[]) connectors ) {
            ports = ports + (Integer)o.getClass().getMethod("getPort").invoke(o) + " ";
        }
        log.info("Tomcat 7.x detected, service {}, engine {}, host {}, stdContext {}, server port: {}",
                service.getClass().getMethod("getName").invoke(service),
                engine.getClass().getMethod("getName").invoke(engine),
                host.getClass().getMethod("getName").invoke(host),
                stdContext.getClass().getMethod("getDisplayName").invoke(stdContext),
                ports);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

}

Merwyn answered 6/10, 2014 at 20:7 Comment(0)
I
1

Based on Khanh's approach, here is a context listener that works for an embedded maven tomcat (v.7.0.62).

Please note the differences ("Tomcat" instead of "Catalina" and no findService("Catalina")), so that the approach works for an embedded tomcat. In contrast to Khanh, I used regular methods instead of reflection to get the BaseDirContext object.

Finally, you should note that you need to call setAliases() on the BaseDirContext object instead of the StandardContext object! Internally, StandardContext's setAliases() is just a setter, whereas BaseDirContext's setAliases() does a lot of other stuff, so that the already running tomcat indeed registers your new aliases.

import org.apache.catalina.Container;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.core.StandardContext;
import org.apache.catalina.core.StandardEngine;
import org.apache.log4j.Logger;
import org.apache.naming.resources.BaseDirContext;
import org.apache.naming.resources.ProxyDirContext;

import javax.management.MBeanServer;
import javax.management.MBeanServerFactory;
import javax.management.ObjectName;
import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;

public class AliasesContextListener implements ServletContextListener {
    private static Logger log = Logger.getLogger(AliasesContextListener.class);

    @Override
    public void contextInitialized(ServletContextEvent sce) {
        try {
            String aliases = "/foo=C:\\bar";

            //get current tomcat server, engine and context objects
            MBeanServer mBeanServer = MBeanServerFactory.findMBeanServer(null).get(0);
            ObjectName name = new ObjectName("Tomcat", "type", "Server");
            Server server = (Server) mBeanServer.getAttribute(name, "managedResource");
            Service[] services = server.findServices();
            StandardEngine engine = (StandardEngine) services[0].getContainer();
            Container defaultHostContainer = engine.findChild(engine.getDefaultHost());

            ServletContext servletContext = sce.getServletContext();
            StandardContext standardContext = (StandardContext) defaultHostContainer.findChild(servletContext.getContextPath());
            ProxyDirContext proxyDirContext = (ProxyDirContext) standardContext.getResources();
            BaseDirContext baseDirContext = (BaseDirContext) proxyDirContext.getDirContext();

            //modify the aliases entry
            baseDirContext.setAliases(aliases);
        } catch (Exception e) {
            log.error("error while setting aliases in context listener", e);
        }
    }

    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        //not implemented
    }
}
Isopleth answered 8/7, 2015 at 16:0 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.