This section describes the core Jetty and Servlet layers that are used in the StrataCode web framework.

Jetty and Servlets

Jetty is a lightweight servlet engine exposed in StrataCode as a set of layers in the jetty layer group. To use jetty with servlets, extend the jetty.servlet layer. There's also a servlet.schtml layer which defines a new extension "schtml" which compiles StrataCode templates into servlets and adds them automatically to web.xml.

See the examples layer called example.servletHelloWorld.core:

public example.servletHelloWorld.core extends jetty.servlet, servlet.schtml {

}

When you extend the jetty.servlet layer, your layer's top level directory can contain web/WEB-INF/web.xml. When you specify this file, it gets put into your build directory. When it is not specified, a default one is generated. This happens because a previous layer defines a web.scxml file - a dynamically generated web.xml. These files the normal rules for layering: a web.scxml file can override a web.xml file and vice versa.

If you want to add a servlet at a specific path, you can use the @PathServlet annotation on any servlet:

file: example/servletHelloWorld/core/HelloWorld.sc
import java.io.IOException;

@PathServlet(path="/hello")
class HelloWorld extends HttpServlet {
   void service(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
      response.getOutputStream().print("<body>Hello world</body>");
   }
}
Or add a schtml file - it will be registered in web.xml using a path based on its generated type name:
file: example/servletHelloWorld/core/Hello.schtml
<html>
<%! String message = "Hello World"; %>

<body><%= message %></body>
</html>

In this case, you request the servlet with http://localhost:8080/Hello.html

Now, in another layer you can extend that layer but change the message property:

file: example/servletHelloWorld/subLayer1/Hello.schtml
<html>
<%! message = "Hello StrataCode"; %>
</html>
or do the same thing from a StrataCode file instead of a schtml file:
file: example/servletHelloWorld/subLayer2/Hello.sc
Hello {
   message = "Hello StrataCode";
}

The web.xml file is generated by adding tags for all servlets, servlet filters, and template pages in the application. When you extend the wicket layer, wicket applications are further inserted into the web.xml file.

This is accomplished by layering of web.scxml files. First in the servlet/webApp layer, the template language is used to define a structured template matching the web.xml file structure of servlets, servlet-mappings, and servlet-filters, filter-mappings.

file: servlet/webApp/web/WEB-INF/web.scxml
<!DOCTYPE web-app
          PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
          "http://java.sun.com/dtd/web-app_2_3.dtd">

<%! 
   public String displayName = "Servlets on StrataCode";
   public int sessionTimeout = 15;

   public StringBuilder outputServletTags() { 
      StringBuilder out = new StringBuilder();
      sc.layer.LayeredSystem system = sc.layer.LayeredSystem.getCurrent();
      sc.layer.BuildInfo bi = system.currentBuildLayer.buildInfo;
      java.util.List<sc.layer.TypeGroupMember> servlets = bi.getTypeGroupMembers("servlets");

      for (sc.layer.TypeGroupMember servlet:servlets) { %>
        <servlet>
          <servlet-name><%= servlet.typeName %></servlet-name>
          <servlet-class><%= servlet.typeName %></servlet-class>
        </servlet>
      <% } 
      return out;
   }

   public StringBuilder outputServletMappingTags() { 
      StringBuilder out = new StringBuilder();
      sc.layer.LayeredSystem system = sc.layer.LayeredSystem.getCurrent();
      sc.layer.BuildInfo bi = system.currentBuildLayer.buildInfo;
      java.util.List<sc.layer.TypeGroupMember> servlets = bi.getTypeGroupMembers("servlets");

      for (sc.layer.TypeGroupMember servlet:servlets) { %>
        <servlet-mapping>
           <servlet-name><%= servlet.typeName %></servlet-name>
           <url-pattern><%= servlet.getAnnotationValue("sc.servlet.PathServlet","path") %></url-pattern>
        </servlet-mapping>
      <% } 
      return out;
   }

   public StringBuilder outputServletFilterTags() { 
      StringBuilder out = new StringBuilder();
      sc.layer.LayeredSystem system = sc.layer.LayeredSystem.getCurrent();
      sc.layer.BuildInfo bi = system.currentBuildLayer.buildInfo;
      java.util.List<sc.layer.TypeGroupMember> filters = bi.getTypeGroupMembers("servletFilters");

      for (sc.layer.TypeGroupMember filter:filters) { %>
         <filter>
              <filter-name><%=filter.typeName %></filter-name>
              <filter-class><%=filter.typeName%></filter-class>
         </filter>
      <% }
      return out;
   }

   public StringBuilder outputServletFilterMappingTags() { 
      StringBuilder out = new StringBuilder();
      sc.layer.LayeredSystem system = sc.layer.LayeredSystem.getCurrent();
      sc.layer.BuildInfo bi = system.currentBuildLayer.buildInfo;
      java.util.List<sc.layer.TypeGroupMember> filters = bi.getTypeGroupMembers("servletFilters");

      for (sc.layer.TypeGroupMember filter:filters) { %>
         <filter-mapping>
              <filter-name><%=filter.typeName %></filter-name>
              <url-pattern><%= filter.getAnnotationValue("sc.servlet.PathServletFilter", "path")%></url-pattern>
         </filter-mapping>
      <% }
      return out;
   }

   public StringBuilder outputListeners() { 
      return new StringBuilder();
   }

   public StringBuilder outputResourceRefs() {
      StringBuilder out = new StringBuilder();
      sc.layer.LayeredSystem system = sc.layer.LayeredSystem.getCurrent();
      if (system.activeDataSources == null)
         return out;
      for (sc.db.DBDataSource dataSource:system.activeDataSources) { %>
         <resource-ref>
            <res-ref-name><%= dataSource.jndiName %></res-ref-name>
            <res-type>javax.sql.DataSource</res-type>
            <res-auth>Container</res-auth>
         </resource-ref>
      <% }
      return out;
   }
%>
   
<web-app>
  <display-name><%= displayName %></display-name>
  <session-config>
     <session-timeout><%= sessionTimeout %></session-timeout>
  </session-config>

  <%= outputServletFilterTags() %>
  <%= outputServletFilterMappingTags() %>
  <%= outputListeners() %>
  <%= outputServletTags() %>
  <%= outputServletMappingTags() %>
  <%= outputResourceRefs() %>

</web-app>
Wicket needs to add its own servlet filters to the list. It defines its own template which modifies the web component, overriding the two methods used to add a new servlet filter:
file: wicket/core/web/WEB-INF/web.scxml
<%! web {
   public StringBuilder outputServletFilterTags() {
      StringBuilder out = super.outputServletFilterTags();

      sc.layer.LayeredSystem system = sc.layer.LayeredSystem.getCurrent();
      sc.layer.BuildInfo bi = system.currentBuildLayer.buildInfo;
      java.util.List<sc.layer.TypeGroupMember> wicketApps = bi.getTypeGroupMembers("wicketApplications");

      for (sc.layer.TypeGroupMember wicketApp:wicketApps) { %>
        <filter>
           <filter-name><%= wicketApp.typeName%></filter-name>
           <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class>
           <init-param>
              <param-name>applicationClassName</param-name>
              <param-value><%= wicketApp.typeName%></param-value>
           </init-param>
        </filter>
      <% }
      return out;
   }

   public StringBuilder outputServletFilterMappingTags() {
      StringBuilder out = super.outputServletFilterMappingTags();

      sc.layer.LayeredSystem system = sc.layer.LayeredSystem.getCurrent();
      sc.layer.BuildInfo bi = system.currentBuildLayer.buildInfo;
      java.util.List<sc.layer.TypeGroupMember> wicketApps = bi.getTypeGroupMembers("wicketApplications");

      for (sc.layer.TypeGroupMember wicketApp:wicketApps) { %>
         <filter-mapping>
            <filter-name><%= wicketApp.typeName%></filter-name>
            <url-pattern><%= wicketApp.getAnnotationValue("sc.wicket.ApplicationPath")%></url-pattern>
         </filter-mapping>
      <% }
      return out;
   }
} %>

<%= super.output() %>

The ability to gather a list of all the objects with a specific annotation or that implement a class is built into the StrataCode build system - accessible to template files at build time. In these templates, you can use various utility libraries to get annotation values, or introspect the types so that this code generation is as informed as necessary. This is all done in Java by framework developers. This work helps keep framework details out of application layers. But since it's using StrataCode code, its easy to use these templates to trace down problems when things go wrong. All project definition files are also StrataCode so when an annotation or other construct causes an error, you can trace those errors as well.

Configuring Jetty

When you extend the jetty/serve layer, you get a default Jetty configuration of a CServer component defined as:

file: jetty/serve/CServer.sc
import org.eclipse.jetty.webapp.Configuration;
import sc.obj.ScopeDefinition;
import sc.sync.SyncManager;

object CServer extends Server {
   object httpConnector extends ServerConnector {
      // TODO: should this value be derived from system.URL at some point?
      port = 8080;
   }
   
   object handlerList extends HandlerList {
      object resourceHandler extends ResourceHandler {
         welcomeFiles = {"index.html"};
         // Run the server from the web subdirectory. 
         resourceBase = "./web";
      }
      object defaultHandler extends DefaultHandler {
      }
   }

   boolean sync = false;

   boolean stopped = false;

   static CServer theServer;

   // Adds support for java:comp/env jndi name space and some other J2EE stuff
   // Using _init because this class uses IAltComponent to avoid conflicts with Jetty's init method
   void _init() {
      Configuration.ClassList classlist = Configuration.ClassList
            .setServerDefault(server);
      classlist.addAfter(
          "org.eclipse.jetty.webapp.FragmentConfiguration",
          "org.eclipse.jetty.plus.webapp.EnvConfiguration",
          "org.eclipse.jetty.plus.webapp.PlusConfiguration");
      /*
      classlist.addBefore("org.eclipse.jetty.webapp.JettyWebXmlConfiguration",
          "org.eclipse.jetty.annotations.AnnotationConfiguration");
       */
   }
   
   //@sc.obj.MainSettings(produceScript = true, execName = "startSCJetty", debug = false, stopMethod="stopServer")
   @sc.obj.MainSettings(produceScript = true, execName = "startSCJetty", debug = false,
                        stopMethod="stopServer", produceJar=true, includeDepsInJar=true)
   static void main(String[] args) throws Exception {
      System.setProperty("java.naming.factory.url.pkgs", "org.eclipse.jetty.jndi");
      System.setProperty("java.naming.factory.initial", "org.eclipse.jetty.jndi.InitialContextFactory");

      if (args.length > 0) {
         for (String arg:args) {
            if (arg.equals("-vh")) {
               sc.lang.html.Element.verbose = true;
            }
            else if (arg.equals("-vha")) {
               sc.lang.html.Element.trace = true;
            }
            else if (arg.equals("-vs"))
               sc.sync.SyncManager.trace = true;
            else if (arg.equals("-vsa")) {
               SyncManager.trace = true;
               SyncManager.traceAll = true;
               SyncManager.verbose = true;
               ScopeDefinition.trace = true;
               ScopeDefinition.verbose = true;
               ScopeDefinition.trace = true;
            }
            else if (arg.equals("-vdb")) {
               sc.db.DBUtil.verbose = true;
            }
            else if (arg.equals("-vlck")) {
               ScopeDefinition.traceLocks = true;
            }
            else if (arg.equals("-vsv")) {
               SyncManager.verbose = true;
               ScopeDefinition.verbose = true;
            }
            else if (arg.equals("-vp")) {
               sc.util.PerfMon.enabled = true;
            }
         }
      }

      CServer s = theServer = CServer;
      if (s.sync)
         s.join();
   }

   void startShutdown() {
   }

   static void stopServer() {
      try {
         if (theServer != null) {
            theServer.startShutdown();
            theServer.stop();
         }
         // else - we may not have called 'main' in if run 'scc -c ...'
      }
      catch (Exception exc) {
         System.err.println("*** Failed to stop jetty server: " + exc);
         exc.printStackTrace();
      }
   }
}
To change the port just extend this layer and override the httpConnector.port property:
file: jetty/examp/CServer.sc
CServer {
   httpConnector {
      port = 8080;
   }
   handlerList {
      webAppHandler {
         contextPath = "/";
         war="./web";
      }
   }
}

JUnit

Extend the junit.main layer. Add the normal @Test annotations for junit. To run your tests, use:

scc yourLayer -t "<testclasspattern>"

for example:

scc test/GetSet -t "sc.*"

or just use -ta to run all tests:

scc test/GetSet -ta

StrataCode provides facilities to gather all tests registered. In this case, junit registers itself as a test provider when you include the junit layer. It's annotation processor for Test accumulates test objects at build time. This index is used to make running all tests easy - a one step compile and run process.