27.7. Custom servlets

It is possible for an extension to include servlets without having to register those servlets in Tomcat's WEB-INF/web.xml file. The extension needs to be in a JAR file as usual. The servlet class should be located in the JAR file following regular Java conventions. Eg. The class my.domain.ServletClass should be located at my/domain/ServletClass.class. You also need to create a second XML file that contains the servlet definitions at META-INF/servlets.xml. The format for defining servlets in this file is very similar to how servlets are defined in the web.xml file. Here is an example:


<?xml version="1.0" encoding="UTF-8" ?>
<servlets xmlns="http://base.thep.lu.se/servlets.xsd">
   <servlet>
      <servlet-name>HelloWorld</servlet-name>
      <servlet-class>net.sf.basedb.examples.extensions.HelloWorldServlet</servlet-class>
      <init-param>
         <param-name>template</param-name>
         <param-value>Hello {user}! Welcome to the Servlet world!</param-value>
      </init-param>
   </servlet>
</servlets>

The <servlets> tag is the root tag and is needed to set up the namespace and schema validation. This may contain any number of <servlet> tags, each one defining a single servlet.

The <servlet-name> tag contains the name of the servlet. This is a required tag and must be unique among the servlets defined by this extension. Other extensions may use the same name without any problems.

The <servlet-class> tag contains the name of implementing class. This is required and the class must implement the Servlet interface and have a public, no-argument constructor. We recommend that servlet implementations instead extends the HttpServlet class. This will make the servlet programming easier.

A servlet may have any number <init-param> tags, containing initialisation parameters for the servlet. Here is the code for the servlet references in the above example.


public class HelloWorldServlet 
   extends HttpServlet 
{
   private String template;
   public HelloWorldServlet()
   {}

   @Override
   public void init()
      throws ServletException
   {
      ServletConfig cfg = getServletConfig();
      template = cfg.getInitParameter("template");
      if (template == null) template = "Hello {user}.";
   }

   @Override
   protected void doGet(HttpServletRequest request, HttpServletResponse response)
      throws ServletException, IOException
   {
      final SessionControl sc = Base.getExistingSessionControl(request, true);
      final DbControl dc = sc.newDbControl();
      try
      {
         User current = User.getById(dc, sc.getLoggedInUserId());
         PrintWriter out = response.getWriter();
         out.print(template.replace("{user}", current.getName()));
      }
      finally
      {
         if (dc != null) dc.close();
      }
   }
   @Override
   protected void doPost(HttpServletRequest req, HttpServletResponse resp)
      throws ServletException, IOException
   {
      doGet(req, resp);
   }
}

Invoking the servlet is done with a URL that is constructed like: $HOME$/[servlet-name].servlet, where $HOME$ is the home directory of the extension. Since BASE 2.13 an alternate URL that doesn't require the .servlet extension is available: $SERVLET_HOME$/[servlet-name], where $SERVLET_HOME$ is the home directory of servlets for the extension. Note that this directory is on a different sub-path than the $HOME$ directory.

Extra path information is supported (since BASE 2.10) if it is inserted between the servlet name and the .servlet extension: $HOME$/[servlet-name][/extra/path/info].servlet, $SERVLET_HOME$/[servlet-name][/extra/path/info]

Query parameters are supported as normal: $HOME$/[servlet-name].servlet?param1=value&param2=value, $SERVLET_HOME$/[servlet-name]?param1=value&param2=value


<extension
   id="net.sf.basedb.clients.web.menu.extensions.helloservletworld"
   extends="net.sf.basedb.clients.web.menu.extensions"
   >
   <index>5</index>
   <about>
      <name>Hello Servlet world</name>
      <description>
         This example uses a custom Servlet page to display the
         "Hello world" message instead of a javascript popup.
      </description>
   </about>
   <action-factory>
      <factory-class>
         net.sf.basedb.clients.web.extensions.menu.FixedMenuItemFactory
      </factory-class>
      <parameters>
         <title>Hello Servlet world!</title>
         <tooltip>Opens a Servlet generated page with the message</tooltip>
         <onClick>
           Main.openPopup('$HOME$/HelloWorld.servlet?ID=' + getSessionId(), 'HelloServletWorld', 400, 300)
         </onClick>
         <icon>~/images/servlet.png</icon>
      </parameters>
   </action-factory>
</extension>

[Note] Note

To keep things as simple as possible, a new instance of the servlet class is created for each request. If the servlet needs complex or expensive initialisation, that should be externalised to other classes that the servlet can use.