Results for category "Application Server"

16 Articles

Why JPAs persistence.xml sucks

Have you ever tried to develop an Java 6 EE application on different application servers? In production we are forced to use WebSphere AS. I like the configuration interface but that’s all. WAS is not usable during development because the deployment cycles are way too long. Because of this we use JBoss AS 7.1.1 in our development environment. Our application uses Java 6 EE features in service (EJB, CDI) and presentation (JSF, CDI) layer but still uses a DAO layer which is managed by Spring. The DAOs get injected by SpringBeanAutowiringInterceptor. For consistency I had planned to port the Spring DAO layer to Java 6 EE.

First of all our Spring configuration uses the correct database connection settings (Hibernate transaction manager, JNDI name) by a simple environment switch which can be set in the application server. By default, the production configuration for WebSphere is used. If you populate an environment key jndi-jboss, the JBoss settings are loaded on startup. This approach introduced a new architectural complexity but fits exactly our needs. Using JPAs persistence.xml and reaching the same goal should be doable, right?

Well… no. First of all, the JPA configuration is simply not designed to handle different environments. This would not be a problem if an application server specific file like jboss-persistence.xml or ibm-persistence.xml would be used by JBoss repsectively WebSphere. On application startup the application server would load the designated persistence.xml and everything is fine.
My approach was to write a simple parser for properties inside persistence.xml which can be evaluated against system properties, like

 		<!-- Remove the hibernate.transaction.manager_lookup_class setting -->
 		<property name="?(applicationserver.runtime=jboss)hibernate.transaction.manager_lookup_class" value="" />
 		<!-- Overwrite the setting -->
 		<property name="?(applicationserver.runtime=jboss)jta.UserTransaction" value="java:comp/JBossUserTransaction" />

Writing and testing the parser was an easy task so I tried to integrate it in the startup process. JPA has no InitializePersistenceContext handler or something else which is executed on startup. The only possibilty was to extend the Hibernate persistence provider and define my own provider inside the persistence.xml. The idea seemed good but did not work. Persistence providers must be deployed in the application server. JBoss only threw an Persistence Provider not found exception. While I was searching for an easier solution (which probably does not exist – you must deploy the provider inside the application server, at least in JBoss) I came upon a blog post which said, that it is not possible to reference a data source in persistence.xml through a res-ref-name resource. Huh? I do need this, otherwise I am not able to specify the JNDI entry independently from my application server. My jboss-web.xml contains a reference to the JBoss data source while the original web.xml holds the reference to the WebSphere data source.
I reached the point where I decided not to go with a pure Java EE 6 implementation and keep the Spring backend. The only solution is adjusting our Maven build process and creating different EAR artifacts for JBoss and WebSphere, which does not solve the problem to easily deploy the application through Eclipse into one of the application servers.

Update (2014-01-29): I opened a feature request in JBoss’ JIRA (https://issues.jboss.org/browse/WFLY-2816) and put a message on the mailing list. A solution for this problem is to add a check for a jboss-persistence.xml in favour of the original persistence.xml.

Hibernate uses wrong schema during schema validation

Recently I struggled upon the same problem, this guy described. Our Oracle database instance contains multiple schematics with almost the same structure. Every developer has it’s own schema for unit and integration tests. On application startup the Hibernate schema validator calls the DatabaseMetaData.getTables() for every linked entity. The method returns the first table which could be found in any schema. The returned tables are by default ordered by schema name. Side node: I would expect that the home schema of the current user would be prefered. This leads to situation that sometimes the validation fails: a user has already migrated his own schema (schema name app_user_unittest) but the schema for the build server (schema name app_build_unittest) still has the old schema version.

Overwriting DatabaseMetaData.getTables() method is not possible as it resides in the Oracle JDBC driver. Instead, you can use the environment variable hibernate.default_schema which points to to prefered schema. Depending on your development environment, the variable could be set during application startup by the application itself or by a system property through your application server.

Use Confluence Play SQL Plug-in with MySQL

Play SQL is a an Atlassian Confluence plug-in for querying database tables and displaying the results inside a Confluence page. The plug-in has only native support for PostgreSQL and HSQL but other drivers can be used via a JNDI datasource.

For using MySQL with Play SQL you have to download the latest MySQL JDBC driver and extract the .jar file to your confluence/lib directory as the Tomcat container searches this path for additional libraries. Now register the new JNDI datasource by adding the following code to your confluence/conf/server.xml:

<pre>
  <Engine name="Standalone" defaultHost="localhost" debug="0">
    <Host name="localhost" debug="0" appBase="webapps" unpackWARs="true" autoDeploy="false">
      <Context path="" docBase="../confluence" debug="0" reloadable="false" useHttpOnly="true">
        <Resource
          name="jdbc/my_datasource"
          auth="Container"
          type="javax.sql.DataSource"
          url="jdbc:mysql://${SERVER}/${DATABASE}?user=${DATABASE}&amp;password=${PASSWORD}"
          driverClassName="org.gjt.mm.mysql.Driver"
          maxActive="20" maxIdle="10" maxWait="20000" />

          <Manager pathname="" />
      </Context>
    </Host>
  </Engine>
</pre>

Restart your Confluence instance (/etc/init.d/confluence restart) and open Play SQL configuration page inside the Confluence administration page. Change the Global Connection to Type: Use JNDI connection, Dialect: Generic and JNDI Name: java:comp/env/jdbc/my_datasource.
That’s it.

NuGet and TeamCity: Repository for third party libraries

Later this day I switched over to another task. TeamCity provides a fully functional NuGet repository server. My plan was migrating our own libraries and some 3rd party libraries from a direct dependency (binary file or as Git submodule) inside the project Git repositores to a system like Maven or Ivy or … well … NuGet for .NET.

First I set up a new Git repository containing all our 3rd party dependencies which are not available in the public NuGet repositories. The Git repository had the following structure

/.nuget
  /NuGet.config
  /NuGet.exe
  /NuGet.targets
/External.Lib.1
  External.Lib.1.nuspec
  /lib
    External.Lib.1.dl
/External.Lib.2
  External.Lib.2.nuspec
  /lib
    External.Lib.2.dl

The .nuget directory came from Visual Studio. I used the NuGet.exe binary for local testing.

In TeamCity I created a new Build Configuration and chose **/*.nupkg as Artifact Path.

The rest of the Build Step configuration can be seen on the attached image.

After running the first build I realized that the .nupkg had the wrong directory structure. The structure External.Lib.1.nupkg for example was

/External.Lib.1
  External.Lib.1.nuspec
  /lib
    External.Lib.1.dl

This is not the expected directory tree.

Reason for that is, that TeamCity automatically appends the parameter -BasePath to the NuGet.exe statement resulting in (taken from build log)

NuGet command: <tc>\buildAgent\tools\NuGet.CommandLine.2.0.40001.nupkg\tools\NuGet.exe pack <tc>\buildAgent\work\80f29a1b916940aa\External.Lib.1\External.Lib.1.nuspec -OutputDirectory <tc>\buildAgent\work\80f29a1b916940aa\dist -BasePath <tc>\buildAgent\work\80f29a1b916940aa -Verbose -Version 0.7 -Properties Configuration=Release

There is no frontend option to disable the setting of BasePath parameter. The configuration parameter Additional commandline arguments did not work as expected as it quotes the entire string.

Thanks to the  parameter Build Path > Properties we can do a little bit of quoting magic and overwrite the BasePath option

Configuration=Release " -BasePath "

Yes, there are two quotes as the usage of Properties puts the NuGet command itself in quotation marks.

The complete command resolves to

... -BasePath <tc>\buildAgent\work\80f29a1b916940aa -Verbose -Version 0.15 -Properties "Configuration=Release " -BasePath ""
and everything is fine 😉

Unity 2.0 in eine ASP.NET MVC 2 Umgebung integrieren

Für ein aktuelles Projekt entwickeln wir mit .NET Framework 3.5 und ASP.NET MVC 2. Um von der fest-codierten Instanzierung von Objekten Abstand zu nehmen (u.a. für das Mocking) und die Applikation frei konfigurierbar zu machen, habe ich mir mal Unity 2.0 angeschaut. Leider beziehen sich die meisten Tutorials und Blog-Posts zu Unity auf eine andere Version, deshalb folgt hier ein kleines Beispiel.

Zuerst einmal müssen die DLLs von Unity dem Projekt hinzugefügt werden. Dann erstellen wir unser DAL:

namespace Test.Dal
{
    public class Benutzer
    {
    }

    public class BenutzerDal : IBenutzerDal
    {
        public IEnumeration<Benutzer> FindAll()
        {
            List<Benutzer> r = new List<Benutzer>();
            r.add(new Benutzer());
            return r;
        }
    }

    public interface IBenutzerDal
    {
        IEnumeration<Benutzer> FindAll();
    }
}

Natürlich haben wir – wie es sich für eine Web-Anwendung gehört, auch den passenden Controller, der folgendermaßen aufgebaut ist:

namespace Test.Controller
{
    public class BenutzerController : Controller
    {
        private IBenutzerDal benutzerDal;

        public BenutzerController(IBenutzerDal benutzerDal)
        {
            this.benutzerDal = benutzerDal;
        }

        public ActionResult Index()
        {
            return View(benutzerDal.FindAll());
        }
    }
}

Auf die passende View habe ich mal verzichtet, da dies selbsterklärend sein sollte.

Standardmäßig werden alle Controller von ASP.NET automatisch erstellt. Damit nun Unity die Factory-Methode für die Controller übernimmt, müssen wir in der Global.asax.cs eine neue Controller-Factory schreiben:

namespace Test
{
    public class MvcApplication : System.Web.HttpApplication
    {
        // Konstante, um später darauf zugreifen zu können
        public const string UNITY_CONTAINER = "unityContainer";

        protected void Application_Start()
        {
            // RegisterRoutes(RouteTable.Routes); u.s.w.

            // Container erstellen
            var container = new UnityContainer();
            // Sektion "unity" aus der Web.config laden
            UnityConfigurationSection section = (UnityConfigurationSection)ConfigurationManager.GetSection("unity");

            // XML-Sektion in den Container laden
            container.LoadConfiguration(section);
            // Neue Controller-Factory erzeugen
            var controllerFactory = new UnityControllerFactory(container);
            // Standard ControllerFactory durch unsere eigene ersetzen
            ControllerBuilder.Current.SetControllerFactory(controllerFactory);
            // Den Unity-Container in der gesamten Applikation verfügbar machen
            Application[UNITY_CONTAINER] = container;
        }

    public class UnityControllerFactory : DefaultControllerFactory
    {
        private readonly IUnityContainer container;

        public UnityControllerFactory(IUnityContainer container)
        {
            this.container = container;
        }

        protected override IController GetControllerInstance(RequestContext requestContext, Type controllerType)
        {
            IController r = container.Resolve(controllerType) as IController;
            return r;
        }
    }
}

Mit diesem Code wird die Standard-ControllerFactory durch unsere eigene Implementierung ersetzt, die wiederum an den ApplicationContext Unity-Container delegiert.

Den Container selbst habe ich noch einmal global in der Applikation verfügbar gemacht, so dass wir darauf auch bei Filterklassen zurückgreifen können.
Vorstellbar wäre, dass bei jedem Request die aktuellen Rechte des Benutzers in der Datenbank überprüft werden sollen (ja, geht auch mit AuthorizationAttribute…). Unser Filter schaut folgendermaßen aus:

namespace Test.Filter
{
    public class MyFilter : IHttpModule
    {
        private HttpApplication httpApplication;

        private UnityContainer unityContainer;

        private IBenutzerDal benutzerDal;

        public void Init(HttpApplication httpApplication)
        {
            this.httpApplication = httpApplication;
            // globalen Container aus der Applikation ziehen
            unityContainer = (UnityContainer)this.httpApplication.Application[MvcApplication.UNITY_CONTAINER];
            // Unity anweisen, dass eine Instanz der Klasse geladen werden soll, die IBenutzerDal implementiert
            benutzerDal = unityContainer.Resolve<IBenutzerDal>();
            // Handler zuweisen... etc.
        }
    }
}

Mit diesem Code sorgen wir für die Verfügbarkeit des Unity-Containers innerhalb eigener Filter.

Letztendlich fehlen noch die Einträge in der web.xml bzw. dispachter-servlet.xml Web.config:

<configuration>
  <configSections>
    <section name="unity" type="Microsoft.Practices.Unity.Configuration.UnityConfigurationSection, Microsoft.Practices.Unity.Configuration" />
  </configSections>
  <unity xmlns="http://schemas.microsoft.com/practices/2010/unity">
    <alias alias="IBenutzerDal" type="Test.Dal.IBenutzerDal, Test" />
    <alias alias="BenutzerController" type="Test.Controller.BenutzerController, Test" />
    <container>
      <register type="IBenutzerDal" mapTo="Test.Dal.BenutzerDal, Test "/>
      <register type="BenutzerController" />
    </container>
 </unity>
 <system.web>
 <!-- ... -->
 <httpModules>
 <!-- andere Module ... und dann unser Filter -->
 <add name="meinFilter" type="Test.Filter.MyFilter, Test"/>
 </httpModules>
 </system.web>
</configuration>

Über das Tag alias lassen sich für Klassen oder Interfaces Aliase definieren, so dass man diese nicht immer wieder vollständig anzugeben braucht. Das Attribut type legt fest, welche Klasse/welches Interface (Test.Dal.IBenutzerDal) in welchem Namespace (Test) hinter dem Alias steckt.
Im Abschnitt container wird nun jede Klasse eingetragen, die in der Applikation verfügbar sein soll. In der ersten Zeile wird ein Typ mit dem Interface Test.Dal.IBenutzerDal registriert, hinter dem sich die Konkrete Klasse (mapTo) Test.Dal.BenutzerDal versteckt. In der zweiten Zeile wird ein Typ Test.Controller.BenutzerController registriert, hinter  dem sich eine Instanz von Test.Controller.BenutzerController versteckt.

Unity überprüft bei jeder Initalisierung (d.h. Erzeugung der Objekte), welche Argumente der Konstruktor des Typs entgegen nimmt und löst diese anhand der bereits registrierten Typen auf. Aus diesem Grund brauche ich beim BenutzerController auch nicht das IBenutzerDal als Konstruktor-Parameter übergeben.
Weiterhin werden alle Objekte standardmäßig als Singleton registriert. Das Verhalten lässt sich aber ändern.

Mehr zu Unity gibt es in der Dokumentation, besonders empfehlenswert ist die Erklärung zum XML-Schema von Microsoft, wo dann u.a. auch Elemente wie property zum Injizieren von Eigenschaften nach Objekterzeugung besprochen werden.

.NET aus der Sicht eines Java-Entwicklers

Die letzten beiden Tage war ich damit beschäftigt, ein paar Evaluierungen für die .NET-Plattform zu machen. Eines unserer Projekte greift mit Hilfe eines (zugegebenermaßen ziemlich coolen) WPF-Frontends über WCF auf einen SOAP-Service zu, der die Verbindung zu einer MS SQL-Datenbank herstellt.

Logging auf die Konsole

Unter Java bzw. innerhalb eines Application-Servers ist es kein Problem, ein simples System.out.println() oder eben log.debug() zu benutzen. Die Ausgaben erscheinen dann jeweils in der Konsole der Entwicklungsumgebung. Wer jetzt denkt: so etwas geht doch sicherlich auch mit ASP.NET, der irrt gewaltig. Wenn innerhalb einer ASP.NET-Anwendung – hier zähle ich unseren WCF-Service einfach mal dazu – auf die Konsole geloggt wird (per log4nets Log.Debug oder aber Console.Write()), verschwinden die Ausgaben im Nirvana. Grund dafür ist, dass der IIS keine Konsole zur Verfügung stellt, in die überhaupt geloggt werden kann.

Entweder man benutzt nun die Trace-Methoden oder aber man schreibt die Logging-Ausgaben von log4net in eine Datei und setzt auf diese dann ein tail ab.

Lebenszyklus eines WCF-Service

Als JEE-Entwickler weiß ich, dass ein Servlet mit dem Start der Anwendung einmalig initalisiert wird und das Servlet dann so lange im Arbeitsspeicher gehalten wird, bis die Applikation heruntergefahren wird.

Bei WCF wird das Attribut InstanceContextMode benutzt, um die Gültigkeit eines Objekts zu beschreiben. Für einen WCF-Service gilt die Standardeinstellung, dass das Objekt (der Service) so lange vorgehalten wird, bis die gegenwärtige Session beendet wird (Einstellung PerSession).

Reihenfolge der Initalisierung von Servlets / Initialkonfiguration

In Anwendungen, die ich nicht mit Spring geschrieben habe, habe ich in meiner web.xml immer ein Servlet definiert, dass nichts weiter macht, als das die Standardeinstellungen gesetzt und z.B. der Logger initalisiert wird.

Bei .NET gibt es so etwas in der Art nicht (sollte ich falsch liegen, bitte ich um baldige Korrektur). Stattdessen muss man über eine selbst definierte ServiceHostFactory diese Konfiguration durchführen. Möglich wäre so etwas:

namespace My.Namespace.Service
{
    public class CustomServiceHostFactory : System.ServiceModel.Activation.ServiceHostFactory
    {
        private Boolean isInitialized = false;

        public override ServiceHostBase CreateServiceHost(string constructorString, Uri[] baseAddresses)
        {
            initalize();
            return base.CreateServiceHost(constructorString, baseAddresses);
        }

        protected override ServiceHost CreateServiceHost(Type serviceType, Uri[] baseAddresses)
        {
            initalize();
            return base.CreateServiceHost(serviceType, baseAddresses);
        }

        private void initalize()
        {
            if (isInitialized)
            {
                return;
            }

            log4net.Config.BasicConfigurator.Configure();
           // Weitere Initialisierungen
        }
    }
}

In der jeweiligen .svc-Datei muss dann die Instanzierung des Service über die CustomServiceHostFactory geschehen:

<%@ ServiceHost Language="C#" Debug="true" Factory="My.Namespace.Service.CustomServiceHostFactory" Service="My.Namespace.Service" CodeBehind="Service.svc.cs" %>

Service-Verbindungen im Client

Hinten über bin ich gefallen, als ich in der app.config des Clients probierte, das Suffix des Service-Endpoints einmalig über

<add key="httpBaseAddress" value="http://localhost" />

zu definieren. Das Attribut httpBaseAddress ist nur auf der Serverseite verfügbar und führt dazu, dass Redundanzen durch doppelte URIs entstehen.
Korrekt schaut das Beispiel nun so aus:

<configuration>
    <appSettings>
    </appSettings>
    <system.serviceModel>
      <!-- CusomtBinding benutzen, da mit wsHttpBinding NTLM nicht korrekt funktioniert -->
        <bindings>
          <customBinding>
            <binding name="Binding_Service">
              <mtomMessageEncoding />
              <security authenticationMode="SecureConversation">
                <secureConversationBootstrap authenticationMode="SspiNegotiated" />
              </security>
              <httpTransport authenticationScheme="Ntlm"/>
            </binding>
          </customBinding>
        </bindings>
        <client>
          <endpoint address="http://localhost:52264/Service.svc"
              binding="customBinding" bindingConfiguration="Binding_Service"
              contract="My.Namespace.Service.IService" name="Binding_IService">
            <identity>
              <dns value="localhost" />
            </identity>
          </endpoint>
        </client>
    </system.serviceModel>
</configuration>

Im Client wird dann über

ChannelFactory ServiceFactory = new ChannelFactory("Binding_IService");

der Proxy des Services geladen.

Die obige Konfiguration (customBinding) zeigt übrigens den Zugriff auf den Web-Service via NTLM. Mit dem Binding wsHttpBinding funktioniert NTLM nämlich nicht.

Springs FrontController soll keine CSS-, PNG- oder JPG-Dateien verarbeiten oder Wie liefere ich statischen Content aus

Wenn man die grundlegende Funktionsweise von Spring MVC verstanden hat – ich empfehle an dieser Stelle die äußerst vorzügliche offizielle Dokumentation -, kommt man an den Punkt, an dem man auch Bilder oder Stylesheets in die Web-Applikation einbinden möchte. Dafür gibt es zwei Möglichkeiten.

Spezifizieren eines eigenen URL-Suffixes für Request-Mappings

Konzept

Alle Requests bzw. Aktionen, die von einem Controller verarbeitet werden sollen, werden auf ein bestimmtes Suffix gemappt.

Controller

@Controller
class MyController {
    @RequestMapping("/home.do")
    public void ModelAndView home() {
        return new ModelAndView("home");
    }

    @RequestMapping("/test.do")
    public void ModelAndView test() {
        return new ModelAndView("test");
    }
}

*-servlet.xml

<!-- ... Beans ... -->
<bean id="viewResolver"
      class="org.springframework.web.servlet.view.UrlBasedViewResolver">
    <property name="viewClass" value="org.springframework.web.servlet.view.InternalResourceView" />
    <property name="prefix" value="/WEB-INF/jsp/"/>
    <property name="suffix" value=".jsp"/>
</bean>
<!-- ... noch mehr Beans ... -->

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	id="WebApp_ID" version="2.4"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <!-- andere Servlet-Definitionen ... ->
    <servlet>
          <servlet-name>dispatcher</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
          <servlet-name>dispatcher</servlet-name>
          <url-pattern>*.do</url-pattern>
    </servlet-mapping>
</web-app>

Konzept

Anfragen auf URLs der Form *.do werden von dem FrontController verarbeitet. Die URL /context/home.do wird auf die Methode MyController.home() gemappt. Diese liefert die View mit dem Namen home zurück. Die View wird durch den InternalResourceViewResolver unter /WEB-INF/jsp/home.jsp gesucht.

Alle Anfragen, die nicht auf *.do enden (z.B. /context/images/bild.jpg, werden von dem Default-Servlet des Containers verarbeitet. Dieser sucht nach der Datei /images/bild.jpg und liefert diese zurück.

Vorteil bei dieser Methode kann sein, dass man sofort sieht, welche URLs auf Methoden gemappt sind. Allerdings entspricht die URL nicht mehr dem REST-Prinzip.

DefaultServlet wird auf die zu nutzenden Dateiendungen gemappt

Hierbei wird dem Default-Servlet des Servlet-Containers explizit gesagt, welche Dateiendungen er handeln soll.

Controller

@Controller
class MyController {
    @RequestMapping("/home")
    public void ModelAndView home() {
        return new ModelAndView("home");
    }

    @RequestMapping("/test")
    public void ModelAndView test() {
        return new ModelAndView("test");
    }
}

*-servlet.xml

Siehe erstes Beispiel

web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	id="WebApp_ID" version="2.4"
	xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">

    <!-- andere Servlet-Definitionen ... ->
    <!-- Beispiel für Tomcat -->
    <servlet>
          <servlet-name>defaultServlet</servlet-name>
          <servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
    </servlet>

    <!-- Beispiel für Jetty -->
    <servlet>
          <servlet-name>defaultServlet</servlet-name>
          <servlet-class>org.mortbay.jetty.servlet.DefaultServlet</servlet-class>
    </servlet>

    <servlet-mapping>
          <servlet-name>defaultServlet</servlet-name>
          <url-pattern>*.css</url-pattern>
    </servlet-mapping>

    <servlet-mapping>
          <servlet-name>defaultServlet</servlet-name>
          <url-pattern>*.png</url-pattern>
    </servlet-mapping>

    <servlet>
          <servlet-name>dispatcher</servlet-name>
          <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
          <load-on-startup>1</load-on-startup>
    </servlet>

    <servlet-mapping>
          <servlet-name>dispatcher</servlet-name>
          <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>

Konzept

Dem Servlet-Container wird explizit gesagt, dass der statische Content (in unserem Beispiel sind das PNGs und CSS-Dateien) von dem DefaultServlet verarbeitet und zurückgegeben wird. Jede andere Anfrage wird vom FrontController verarbeitet. Vorteil bei dieser Methode ist, dass sich so schöne URLs bauen lassen. Die URL /home wird z.B. auf die Methode MyController.home() gemappt.

Einer der Nachteile ist, dass jeder statische Content, der im Projekt verwendet wird, in die web.xml eingetragen werden muss – wenn z.B. auch noch GIFs oder JPEGs benutzt werden sollen. Außerdem muss die web.xml für jeden Servlet-Container manuell angepasst werden. In der J5EE-Spezifikation existiert keine Notwendigkeit eines Default-Servlets. Bei Jetty und Tomcat wird zwar automatisch der Servlet-Name default für das Default-Servlet registriert, allerdings kann sich das in Zukunft ändern.

Fazit

Wie man statischen Content ausliefert, ist eine Frage des Geschmacks. Ich persönlich finde die zweite Methode eleganter, da sich dadurch REST-konforme URLs schreiben lassen.

Apache Tomcat für Azubis

Nachdem ich vor ein paar Wochen den Artikel über Spring für Azubis veröffentlicht habe, wird es heute Zeit für “Apache Tomcat für Azubis”.
Dieser Artikel hat nicht ansatzweise den Anspruch, die komplette Konfiguration eines Tomcat-Servers zu beschreiben – dafür ist die offizielle Dokumentation gedacht. Hier geht es darum die grobe Funktionsweise zu erklären. Im Folgenden definiere ich Web-Applikation als eine Anwendung, die in Java geschrieben ist und in einem Applikationsserver läuft.

Was ist der Apache Tomcat?

Spätestens ein paar Stunden nach dem Einstieg in die Web-Programmierung mit Java stellt sich die Frage: “Wie bekomme ich den Mist überhaupt zum Laufen?”. Von der Entwicklung mit PHP ist man gewöhnt, dass die Applikation nur in eines der Verzeichnisse des Web-Servers geschoben werden muss und das war es dann schon – klar, PHP muss vorher noch in den Webserver als CGI oder Modul eingebunden sein.

Der Apache Webserver liefert bei einer Anfrage über HTTP oder HTTPS die jeweiligen (geparsten) Dateien zurück.
Nichts Anderes macht der Apache Tomcat auch. Tomcat ist ein Webserver, der speziell auf die Anforderungen von Web-Applikationen unter Java zugeschnitten ist.

Spezifikationen

Einige Hintergrundinfos sind nötig: Über den Java Community Process (JCP) werden in regelmäßigen Abständen so genannte Java Specification Requests (JSR) veröffentlicht. Diese beinhalten Standardisierungen für die Java-Umgebung. Oracle, damals Sun, hat 1997 mit der Version 1.0 die erste Version der Servlet-API definiert und diese mit der Version 2.3 im August 2001 dem JCP zugängig gemacht. Seit dieser Zeit werden Änderungen in der Servlet-API gemeinsam von der Java-Community bestimmt.

Die Servlet-API beschreibt anhand von Interface-Definitionen, welche Schnittstellen ein Applikations-Server implementieren bzw. bereitstellen muss.

Warum die Spezifikation?

Für verschiedene Anwendungsfälle gibt es verschiedene (Java-)Applikationsserver. Der bekannteste unter ihnen ist sicherlich Apache Tomcat. Daneben gibt es noch Jetty (freie Implementierung, ist relativ klein und kann in Embedded-Umgebungen gut eingesetzt werden), Websphere (kommerzielle Lösung von IBM) und einige andere.

Damit nun eine Applikation unter allen Applikationsserver funktionieren kann, muss jeder Server die Servlet-API bereitstellen. Oracle/Sun sichert über Test-Suiten ab, dass die Applikationsserver auch mit der Servlet-API kompatibel sind und den Anforderungen in dem JSR entsprechen.

Der Entwickler einer Webapplikation kann sich nun sicher sein, dass die Anwendung unter jedem Applikationsserver lauffähig ist. Die Entscheidung, welcher Server einzusetzen ist, hängt von den Anforderungen des Kunden oder des persönlichen Geschmacks ab.

Versions-Wirr-War(r)

Mit jeder neuen Version der Servlet-API werden neue Funktionalitäten aufgenommen, die die aktuellen Anforderungen im Web-Bereich widerspiegeln.

Wenn man sich den Eintrag in der englischen Wikipedia zu Servlets anschaut sieht man, dass einige Versionen existieren. Aktuell ist die Version 3.0, die im JSR 315 definiert ist. Eine der größten Erweiterungen ist die Definition von asynchronen Servlets, die durch die starke Verbreitung von AJAX-Funktionalitäten im Internet längst überfällig gewesen ist. Der Apache Tomcat 7 implementiert die Version 3.0 der Servlet-API und kann somit auch die neuen Funktionaliten bereitstellen.

Allerdings wird bei vielen Projekten weiterhin die letzte Version 2.5 eingesetzt – hierfür wird der Apache Tomcat 6 eingesetzt. Da Version 3.0 aber i.a.R. abwärtskompatibel ist, können für die Servlet-API 2.5 auch der Tomcat 7 eingesetzt werden.

Übersicht über die Servlet-API

Die Spezifikation für JSR 153/Servlet 2.5 ist knapp 350 Seiten lang, diese hier alle zusammenzufassen wäre unsinnig. Statt dessen will ich kurz die wichtigsten Themen behandeln.

Jede Web-Applikation basiert auf einem oder mehreren Servlets. Servlets sind Klassen, die von der abstrakten Klassen HttpServlet erben.Diese stellt Methoden bereit, die beim Aufruf des Servlets aufgerufen werden. doGet(…) wird bei einem HTTP-GET-Request, doPost(…) bei einem HTTP-POST-Request u.s.w.

Der Entwickler implementiert nun diese Methoden und kann über die Parameter vom Typ HttpServletRequest (eingehender HTTP-Request) und HttpServletResponse (zu erzeugende HTTP-Antwort) die Ein- und Ausgabe des HTTP-Streams steuern.

Klingt einfach, ist es an sich auch. Frameworks wie Spring kapseln all ihre Methoden um diese drei wichtigsten Klassen – und natürlich noch einigen anderen.

JavaServer Pages / JSP

JSP? WTF? Von PHP sind sicherlich Dateien bekannt, die wild PHP-Quellcode und HTML-Ausgabe mischen. JSP-Dateien sind das Gegenstück zu eben diesen PHP-Dateien. In ihnen kann HTML- und Java-Code parallel stehen.

Intern passiert beim Aufruf einer JSP-Datei nichts weiter, als dass bei ihrem ersten Aufruf die Datei in ein Servlet umgewandelt wird. Alle HTML-Fragemente werden über out.println() ausgegeben. Bei erneutem Aufruf der Datei wird nun das cachete Servlet ausgeführt. Sollte sich die JSP-Datei ändern, wird sie beim nächsten Aufruf neu kompiliert.

Datei web.xml und Ordner WEB-INF

Jede Web-Applikation muss im Hauptverzeichnis den Ordner WEB-INF und darin die Datei WEB-INF/web.xml besitzen. Der Inhalt des Ordners WEB-INF kann von außerhalb, d.h. vom Benutzer nicht aufgerufen werden. Er enthält für die Applikation alle wichtigen Dateien, dazu zählen die kompilierten Java-Dateien (Ordner WEB-INF/classes) und eben die Datei web.xml. Diese beschreibt die grundlegende Konfiguration der Applikation. Die web.xml ist ein so genannter Deployment Descriptor.

web.xml

Kommen wir nun zum simplen Aufbau des Deployment Descriptors

<web-app 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/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
        version="2.5">
       <env-entry>
                <env-entry-name>environmentVariable</env-entry-name>
                <env-entry-value>true</env-entry-value>
                <env-entry-type>java.lang.Boolean</env-entry-type>
        </env-entry>
        <servlet>
                <servlet-name>myServlet</servlet-name>
                <servlet-class>de.ckl.servlet.MyServlet</servlet-class>
                <description>Serlvet</description>
                <init-param>
                        <param-name>debug</param-name>
                        <param-value>true</param-value>
                </init-param>
                <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet>
                <servlet-name>myLaterServlet</servlet-name>
                <servlet-class>de.ckl.servlet.MyLaterServlet</servlet-class>
                <description>Spaeter geladenes Servlet</description>
                <init-param>
                        <param-name>debug</param-name>
                        <param-value>false</param-value>
                </init-param>
                <load-on-startup>10</load-on-startup>
        </servlet>
        <servlet-mapping>
                <servlet-name>myServlet</servlet-name>
                <url-pattern>/servlet/*</url-pattern>
        </servlet-mapping>
        <servlet-mapping>
                <servlet-name>myLaterServlet</servlet-name>
                <url-pattern>/later/*</url-pattern>
        </servlet-mapping>
</web-app>

Das Root-Tag web-app enthält den zu benutzenden Namespace und die dahinter liegende XSD. Wenn die Zuordnung nicht stimmt, kann vom Applikationsserver die Anwendung nicht gestartet werden. Also Achtung!

Das Tag env-entry legt anhand von Key-Value-Paaren applikationsweite Einstellungen fest, die für alle Servlets der Applikation gültig sind.

Über servlet werden nun die einzelnen Servlets definiert. servlet-name muss für jedes Servlet eindeutig sein, servlet-class verweist auf die Klasse, die HttpServlet implementiert. Mit init-param können Servlet-spezifische Einstellungen festgelegt werden. Im obigen Beispiel wird das Debugging für myServlet aktiviert, bei myLaterServlet hingegen deaktiviert.

Damit eine Reihenfolge zum Starten festgelegt werden kann – z.B. um Ressourcen wie Datenbankverbindungen zu initalisieren, wurde das Tag load-on-startup eingeführt. Je kleiner der Wert, desto früher wird das Servlet gestartet.

Wir haben nun also die Servlets und deren Einstellungen konfiguriert. Es geht nun darum, dass die Servlets auch im Browser aufgerufen werden. Im obigen Beispiel werden alle HTTP-Anfragen (das Verb – also GET, POST etc. ist dabei egal) auf die URL http://…/mein_context/servlet/* auf das Servlet myServlet gemappt (siehe erstes servlet-mapping-Tag), die URLs unter http://…/mein_context/later/* hingegen auf auf myLaterServlet.

Deployment

Das Einspielen der Webapplikation in den Tomcat nennt man Deployen. Entweder man hat die Applikation bereits manuell eingerichtet und kopiert die neuen Dateien einfach in das Applikationsverzeichnis oder aber man spielt ein .war ein. Ein WAR (Web Archive) ist nichts weiter als eine ZIP-Datei, die im Verzeichnis webapps des Tomcats kopiert wird. Der Tomcat überwacht den Inhalt des Verzeichnisses und extrahiert bei einer neuen .war-Datei diese in das Verzeichnis webapps/war-datei. Der Vorgang wird Hot-Deployment genannt.

Wir haben beispielsweise die Datei my-test-applikation.war. Diese wird nach webapps kopiert. Der Tomcat entpackt den Inhalt des Archivs automatisch nach webapps/my-test-applikation.

Das Archiv enthält alle Dateien, die von der Web-Applikation benötigt werden. Dazu gehören kompilierte Klassen (/WEB-INF/classes), abhängige Libraries (/WEB-INF/lib), der Deployment-Deskriptor (/WEB-INF/web.xml) und JSP-Dateien, Templates u.s.w.
Als Entwickler muss man immer im Hinterkopf behalten, dass die Anpassung der globalen conf/server.xml bzw. conf/web.xml des Tomcats während des Deployments nichts angetastet werden soll. Dadurch wird sichergestellt, dass die Applikationen zum einen einfach zu deployen und zum anderen unabhängig von dem eingesetzten Application-Server sind. In einem Jetty existiert z.B. keine server.xml!

Context Path

Die Applikation an sich ist dann unter http://localhost/my-test-applikation erreichbar. Den Pfad hinter http://localhost/ wird Context Path genannt und ist unabhängig von dem realen Ordner!

In der Datei conf/server.xml wird automatisch beim Hot Deployment ein neuer Konfigurationseintrag erzeugt:

<Host name="localhost" debug="0" appBase="webapps"
	      unpackWARs="true" autoDeploy="true">
    <Context path="my-test-applikation" docBase="webapps/my-test-applikation" debug="0"/>
</Host>

Über das path-Attribut wird festgelegt, dass alle Anfragen auf die URL http://localhost/my-test-applikation an die Applikation geht, die im Odner (docbase) webapps/my-test-applikation liegt.

Lifecycle

Beim Starten des Tomcats werden alle Applikationen, d.h. jeder definierte Context gestartet. Dazu überprüft Tomcat die Datei web.xml und startet jedes Servlet in der angegebenen Reihenfolge.

Sobald nun eine HTTP-Anfrage an den Tomcat gerichtet ist, wird zuerst überprüft, ob der Context Path definiert ist. Bei Existenz wird an das Servlet übergeben, auf dem das Mapping der URL zutrifft.

Beim Beenden des Tomcats werden alle laufenden Servlets beendet.

Wichtigster Unterschied zu PHP-Webapplikationen

Beim Umstieg von PHP-Webapplikationen zu Java gibt es neben den sprachlichen Unterschieden folgende, gravierende Differenz, die man als Entwickler immer im Hinterkopf haben muss: Unter PHP wird bei jedem Seitenaufruf das Script geparst – bei Java hingegen wird eine Applikation zu Beginn ihres Lebenszyklus gestartet und läuft die ganze Zeit!

Dafür ist es wichtig, dass man die verschiedenen Scopes kennt:

  • Web Context / Application – javax.servlet.ServletContext
    Dieser Kontext umfasst die komplette Applikation und existiert vom Start des Servlets bis zum Herunterfahren
  • Session – javax.servlet.http.HttpSession
    Eine Session unter PHP ist bekannt? Dieser Scope ist identisch dazu.
  • Request – javax.servlet.HttpServletRequest
    Der Request-Scope ist so lange verfügbar, wie eine HTTP-Anfrage dauert.
  • Page – javax.servlet.jsp.PageContext
    Bei der Arbeit mit JSP-Dateien existiert dieser Scope. In ihm sind nur Objekte verfügbar, die innerhalb der Seite definiert worden sind.

Objekte im Scope Application können von allen Servlets gesehen werden, es existiert immer nur ein Application-Scope innerhalb einer Anwendung. Session-Scopes existieren für jeden Benutzer, solange er mit der Applikation kommuniziert. Ein Benutzer kann nicht auf die Session eines anderen zugreifen (zumindest nicht ohne Tricks).
Bei 10 HTTP-Anfragen existieren auch 10 Request-Scopes, die i.a.R. jeweils einer Session zugeordnet sind.

Entwicklung mit Eclipse

Eclipse bzw. WTP bindet den Applikationsserver direkt in die IDE mit ein.

Integration

Zuerst muss unter Preferences -> Server -> Runtime Environments ein neuer Server hinterlegt werden. Dazu muss über den Button Add zuerst die verwendete Tomcat-Version ausgewählt und danach das Installationsverzeichnis angegeben werden. Unter File -> New -> Other -> Server -> Server muss danach ein neuer lokaler Server eingerichtet werden. Im folgenden Dialog muss unter Apache die verwendete Version ausgewählt werden. Die letzte Seite des Wizards mit dem Namen Add and Remove wird dazu verwendet, um Projekte vom Typ Dynamic Web Project automatisch der Tomcat-Instanz zuzuweisen.

Nachträgliche Änderung eines Projekts in Dynamic Web Project

Falls ein Projekt nachträglich in ein Dynamic Web Project umgewandelt werden soll, geschieht das über einen Rechtsklick auf den Projektordner im Project Explorer. Dort muss unter Project Facets der Eintrag Dynamic Web Module ausgewählt werden.
Die Aktivierung sorgt dafür, dass das Projekt von Eclipse als Web-Projekt erkannt wird.

Ordnerstruktur

Eclipse benutzt standardmäßig den Ordner WebContent des Projekts als Hauptverzeichnis für die Dateien, die automatisch im Tomcat deployt werden. Wenn das Verzeichnis nicht WebContent lauten soll, muss im Projektordner die Datei .settings/org.eclipse.wst.common.component angepasst werden. Dort lässt sich definieren, welche Dateien in den Tomcat deployt werden sollen.

Automatisches Deployment

Was macht nun Eclipse beim Starten der lokalen Tomcat-Instanz?

  • Zuerst werden alle Klassen kompiliert, die in einem Source-Ordner liegen.
  • Danach wird, falls noch nicht vorhanden, unter workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmpN/wtpwebapps der Ordner des Context-Paths erstellt – das N bei tmpN steht dabei für die Nummer der jeweiligen Serverinstanz
  • Anhand der XML-Datei .settings/org.eclipse.wst.common.component werden nun alle dort definierten Dateien in den Context-Path kopiert
  • Eclipse startet nun eine neue Tomcat-Instanz, bei der das Verzeichnis mit den Konfigurationsdateien auf Servers -> Apache Tomcat x.x at localhost-config gemappt wird. Somit wird sichergestellt, dass die lokale Tomcat-Installation autark von der Entwicklerinstanz betrieben werden kann.
  • Tomcat lädt die Apache Tomcat x.x at localhost-config/server.xml und erstellt einen neuen Context-Path in der Laufzeitumgebung des Tomcat
  • Alle HTTP(S)-Anfragen des Context-Paths werden somit auf den realen Pfad workspace/.metadata/.plugins/org.eclipse.wst.server.core/tmpN/wtpwebapps/my-test-applikation gemappt

JPA/EclipseLink 2 & Spring 3.0.2: The container policy [IndirectListContainerPolicy(class org.eclipse.persistence.indirection.IndirectList)] is not compatible with transparent indirection

Nachdem ich mir die letzten beiden Tage ein paar Sachen zu JPA und JTA durchgelesen hatte, setzte ich eine kleine Test-Anwendung auf, die mir mit Hilfe von Spring 3.0.2 und JPA/EclipseLink 2 Benutzer und deren Gruppen aus einer Datenbank laden sollte.
Das Datenbankschema dazu war einfach: Tabelle user, Tabelle group – zwischen beiden besteht eine n:m-Beziehung (Zwischentabelle user_in_group)

persistence.xml:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="1.0"
 xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
 <persistence-unit name="WebDefault" transaction-type="RESOURCE_LOCAL">
 <class>de.ecw.project.sql.vo.UserVO</class>
 <class>de.ecw.project.sql.vo.GroupVO</class>
 <properties>
 <property name="eclipselink.ddl-generation" value="drop-and-create-tables"/>
 <property name="eclipselink.ddl-generation.output-mode" value="both"/>
 </properties>
 </persistence-unit>
</persistence>

GroupVO.java

package de.ecw.project.sql.vo;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;

import de.ecw.project.sql.Schema;

@Entity
@Table(name = Schema.GROUP_TABLE)
public class GroupVO
{
    @Id
    @GeneratedValue
    @Column(name = Schema.COLUMN_ID)
    private long id;

    @Column(name = Schema.COLUMN_NAME)
    private String name;

    public void setId(long id)
    {
        this.id = id;
    }

    public long getId()
    {
        return id;
    }

    public void setName(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }
}

UserVO.java

package de.ecw.project.sql.vo;

import java.util.Collection;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OrderBy;
import javax.persistence.Table;

import org.springmodules.validation.bean.conf.loader.annotation.handler.Length;
import org.springmodules.validation.bean.conf.loader.annotation.handler.NotBlank;

import com.thoughtworks.xstream.annotations.XStreamAlias;

import de.ecw.project.sql.Schema;

@XStreamAlias("user")
@Entity
@Table(name = Schema.USER_TABLE)
public class UserVO
{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(name = Schema.COLUMN_ID)
    private long id;

    @NotBlank
    @Length(max = 255, min = 10)
    @Column(name = Schema.USER_COLUMN_USERNAME)
    private String username;

    @NotBlank
    @Length(max = 255, min = 5)
    @Column(name = Schema.COLUMN_NAME)
    private String name;

    @Column(name = Schema.USER_COLUMN_PASSWORD)
    private String password;

    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = Schema.USER_IN_GROUP_TABLE, joinColumns = @JoinColumn(name = Schema.COLUMN_USER_ID), inverseJoinColumns = @JoinColumn(name = Schema.COLUMN_GROUP_ID))
    @OrderBy(Schema.COLUMN_ID + " DESC")
    private Collection<GroupVO> groups;

    public void setName(String name)
    {
        this.name = name;
    }

    public String getName()
    {
        return name;
    }

    public void setUsername(String username)
    {
        this.username = username;
    }

    public String getUsername()
    {
        return username;
    }

    public void setId(long id)
    {
        this.id = id;
    }

    public long getId()
    {
        return id;
    }

    public String toString()
    {
        return "user";
    }

    public void setPassword(String password)
    {
        this.password = password;
    }

    public String getPassword()
    {
        return password;
    }

    public void setGroups(Collection<GroupVO> groups)
    {
        this.groups = groups;
    }

    public Collection<GroupVO> getGroups()
    {
        return groups;
    }
}

dispatcher-servlet.xml

...
	<context:component-scan base-package="de.ecw" />

	<bean id="dataSource" name="dataSource"
		class="org.springframework.jdbc.datasource.DriverManagerDataSource">
		<property name="driverClassName" value="org.gjt.mm.mysql.Driver" />
		<property name="url" value="jdbc:mysql://localhost:3306/test" />
		<property name="username" value="root" />
		<property name="password" value="" />
	</bean>

	<bean id="entityManagerFactory"
		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
		<property name="dataSource" ref="dataSource" />
		<property name="jpaVendorAdapter">
			<bean
				class="org.springframework.orm.jpa.vendor.EclipseLinkJpaVendorAdapter">
				<property name="showSql" value="true" />
				<property name="generateDdl" value="true" />
				<property name="databasePlatform"
					value="org.eclipse.persistence.platform.database.MySQLPlatform" />
			</bean>
		</property>
		<property name="loadTimeWeaver">
			<bean
				class="org.springframework.instrument.classloading.SimpleLoadTimeWeaver" />
		</property>
	</bean>

	<bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<bean id="userDAO" name="userDAO" class="de.ecw.project.sql.dao.impl.UserDAOImpl">
		<property name="entityManagerFactory" ref="entityManagerFactory" />
	</bean>
...

Wenn ich die Anwendung im Tomcat startete, wurden keine Fehler ausgeworfen – sobald aber das erste Mal auf die DAO-Schicht bzw. VOs zugegriffen wurde, schmiss mir EclipseLink folgenden Fehler:

Local Exception Stack:
Exception [EclipseLink-148] (Eclipse Persistence Services - 2.0.0.v20091127-r5931): org.eclipse.persistence.exceptions.DescriptorException
Exception Description: The container policy [IndirectListContainerPolicy(class org.eclipse.persistence.indirection.IndirectList)] is not compatible with transparent indirection.
Mapping: org.eclipse.persistence.mappings.ManyToManyMapping[groups]
Descriptor: RelationalDescriptor(User --> [DatabaseTable(user)])

Bei Google ließ sich bis auf zwei Einträge dazu nichts finden; die in den Threads beschriebenen Lösungsvorschläge brachten auch nichts.

Wenn ich mir die Dokumentation von Spring komplett durchgelesen hätte , wäre ich auch schneller auf die Lösung gekommen:

  • In der dispatcher-servlet.xml muss SimpleLoadTimeWeaver durch InstrumentationLoadTimeWeaver ersetzt werden
  • Die Eclipse-Anwendung muss mit den VM Argumenten -javaagent:
    <Pfad>/WebContent/WEB-INF/lib/org.springframework.instrument-3.0.2.RELEASE.jar
    gestartet werden.
  • Zusätzlich habe ich noch in der server.xml im Kontext der Anwendung (<Context…) den TomcatInstrumentableClassLoader eingebunden (<<Loader loaderClass=”org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader” useSystemClassLoaderAsParent=”false”/>)

Nach einem Neustart des Tomcats lief dann die Anwendung auch und die zugehörgen Datenbanktabellen wurden automatisch erzeugt.

CruiseControl / SVNBootstrapper: Server certificate verification failed: issuer not trusted

Heute habe ich endlich die Zeit gefunden, mich um unseren Buildserver zu kümmern.
Meine erste Aufgabe bestand darin, dass ich die kompletten Konfigurationsdateien so abstrahiert habe, dass die eine Projekt-Konfiguration letztendlich nur noch aus 10 Zeilen XML-Code besteht, in denen u.a. der Pfad zum SVN-Repository angegeben ist.
Dies funktionierte auch alles wunderbar, bis der SVNBootstrapper zum ersten Mal die Verbindung mit dem Repository aufnahm.

Im Log bekam ich folgenden Fehler zu sehen:

2009-09-28 14:15:20,739 [Thread-28] WARN  SVNBootstrapper  – svn: OPTIONS von ¯https://$host/svn/$project/trunk®: Server certificate verification failed: issuer is not trusted (https://$host)
2009-09-28 14:15:21,051 [Thread-27] INFO  Project          – Project $projectidle
2009-09-28 14:15:21,051 [Thread-27] INFO  ProjectController – $project Controller: build progress event: idle
2009-09-28 14:15:21,051 [Thread-27] ERROR Project          – exception attempting build in project $project
net.sourceforge.cruisecontrol.CruiseControlException: svn process exited with error code 1

Nach kurzer Recherche wurde ich fündig: Das Benutzerkonto, unter dem CruiseControl läuft, muss das Client-Zertifikat installieren. Dazu gibt es entweder die Möglichkeit, im Subversion-Konfigurationsverzeichnis die SSL-Settings anzupassen oder aber – einfacher – das Zertifikat im Zertifikate-Speicher zu installieren.
Wichtig ist dabei, dass CruiseControl unter einem lokalen Benutzer-Account als Dienst läuft und nicht als NetworkService oder Local System.
Unter dem Benutzer-Account unter dem CC läuft, muss man sich nun anmelden, Internet Explorer starten, auf die URL des WebDAV-Ordners gehen, das Zertifikat installieren und schließlich den CC-Dienst neu starten. Nun funktioniert alles wunderbar und das Projekt Build-Server geht langsam aber sicher dem Ende entgegen