www.alexander-merz.com | Alexander Merz

Tomcat&PHP - JSP-Tags in PHP

Im heutigen Artikel widmen wir uns JSP-Tags und werde einige Klassen vorstellen, die den Umgang mit JSP-Tags in PHP-Skripten erleichtern.

Diese Artikelserie ist eigentlich ein Nebenprodukt eines Projektes, an dem ich derzeit arbeite: Bei verschiedenen Firmen ist derzeit eine umfangreiche PHP-Anwendung (Groupware, CMS etc.) installiert. Diese PHP-basierte Lösung soll mittelfristig durch eine Java-basierte Enterprise-Lösung abgelöst werden.

Um aber Brüche beim Wechsel der Anwendung zu vermeiden und den Migrationsaufwand für bereits getätigte Kundenanpassungen zu minimieren, besteht die Idee die PHP-Oberfläche weiterlaufen zu lassen, darunter aber Schritt-für-Schritt auf Java umzustellen.

Meine Aufgabe dabei ist herauszufinden, ob dass a) technisch machbar und b) auch ökonomisch sinnvoll ist.

Technisch machbar ist es, durch die bestehende Java-Integration in PHP. Problematisch ist die ökonomische Seite. Den es müsste allein zum Zweck der Migration erhebliche Teile der Java-API in PHP nachgebaut bzw. ergänzt werden. Eine vertretbare Lösung wäre es allerdings, wenn es möglich ist bestehende JSP-Tags einzusetzen. Den dann kann man praktisch einen Großteil der bestehenden Ausgabe-Logik direkt in PHP weiter benutzen. Andererseits würde dabei gleichzeitig die Grundlage geschaffen für den entgültigen Umzug von PHP nach JSP. (Ja, als Mitglied der PHP-Community blutet auch mir in wenig das Herz, so etwas zu sagen)

Die kurze Aussage zum Thema JSP-Tags in PHP: Es geht. Die lange: Damit beschäftigt sich der Rest des Artikels.

Wie JSP-Tags funktionieren

Schauen wir uns an, was eigentlich mit den Tags in einer JSP-Seite passiert. Tomcat erhält einen Request auf eine JSP-Seite. Er liest diese JSP-Seite ein und generiert daraus ein Servlet, daraus eine Java-Class-Datei und führt diese aus. Es sei denn die class-Datei existiert schon und die JSP-Seite wurde nicht geändert – dann führt er die class-Datei direkt aus.

Interessant ist, was bei der Umwandlung in ein Servlet geschieht. Nehmen wir eine einfache JSP-Seite:

<%@ taglib uri="/c4u_core_content" prefix="content" %>
<html>
 <header><title>JSP-Seite</title></header>
 <body>
<content:initpage />
 <%
  out.println("Hallo");
 %>
 </body>
<html>

Am Anfang wird die JSP-Tag-Bibliothek eingebunden, dann erfolgt ganz normaler HTML-Code, <content:initpage /> ist ein JSP-Tag, gefolgt von eingebetten Java-Code und zum Schluss noch mal HTML.

Ein Servlet wird erzeugt

Wird diese JSP-Seite aufgerufen, dann erzeugt Tomcat folgendes Servlet (sehr gekürzt):

public final class test_jsp extends org.apache.jasper.runtime.HttpJspBase
    implements org.apache.jasper.runtime.JspSourceDependent {

private static java.util.Vector _jspx_dependants;

  static {
    _jspx_dependants = new java.util.Vector(1);
    _jspx_dependants.add("/WEB-INF/tlds/c4u/core/content.tld");
  }
...
public void _jspService(HttpServletRequest request, HttpServletResponse response)
        throws java.io.IOException, ServletException {

    JspFactory _jspxFactory = null;
    PageContext pageContext = null;
    HttpSession session = null;
...
try {
      _jspxFactory = JspFactory.getDefaultFactory();
      response.setContentType("text/html");
      pageContext = _jspxFactory.getPageContext(this, request, response,
      			null, true, 8192, true);
     out = pageContext.getOut();
      _jspx_out = out;

      out.write("\r\n");
      out.write("<html>\r\n");
      out.write(" <header><title>JSP-Seite</title></header>\r\n");
      if (_jspx_meth_content_initpage_0(_jspx_page_context))
        return;

  out.println("Hallo");
…
}

private boolean _jspx_meth_content_initpage_0(PageContext _jspx_page_context)
          throws Throwable {
…
   _jspx_th_content_initpage_0.setPageContext(_jspx_page_context);
    int _jspx_eval_content_initpage_0 = _jspx_th_content_initpage_0.doStartTag();
      if (_jspx_th_content_initpage_0.doEndTag() == javax.servlet.jsp.tagext.Tag.SKIP_PAGE)
        return true;
…
}

Wenn Sie der vollständige Quellcode interessiert, dann speichern Sie einfach die JSP-Seite im ContextRoot und rufen Sie die Seite im Browser aus. Das erzeugte Servlet speichert Tomcat im Verzeichnis work/Catalina/localhost/contextname/org/apache/jsp/.

JSP-Tags = Java-Klassen

Entscheidend ist eins: Ein Tag ist nichts anderes als eine Anweisung eine bestimmte Klasse innerhalb des zu erzeugenden Servlets aufzurufen, wie in der Methode __jspx_meth_content_initpage_0 zu sehen. Welche Klasse dies ist, wird bestimmt über die Angabe der Tag-Bibliothek. Die Tag-Klasse muss ein bestimmtes Interface implementieren, damit es mit der automatischen Code-Generierung auch funktioniert, minimal sind das die Methode doStartTag() und doEndTag().

Soweit in Ordnung, ein Tag ist eine Java-Klasse – und eine Java-Klasse und ihre Methoden können wir in PHP ja benutzen. Wir müssen nur einen Blick in die content.tld-Datei werfen und herausfinden, welche Klasse das Tag implementiert. Also probieren wir flugs folgendes:

<?php
$tag = new Java("com.c4u.eis.core.view.tags.content.InitpageTag");
$tag->doStartTag();
$tag->doEndTag();
?>

... und wir werden grandios scheitern.

Und sie bewegt sich doch!

Wie im Servlet-Quellcode ersichtlich, benötigen wir ein pageContext-Objekt, um die Tag-Klasse zu initialisieren. Aber woher bekommen wir dass? An dieser Stellen müssten wir uns in die Untiefen der Tomcat-Programmierung begeben. Um es abzukürzen: Es gibt die Klasse org.apache.jasper.runtime.JspFactoryImpl. Sie liefert uns ein pageContext-Objekt, wenn wir Sie mit den richtigen Objekten füttern. Und ob Zufall oder nicht, es sind genau jene, die Tomcat uns zur Verfügung stellt. Damit können wir Folgendes schreiben:

<?php
$factory = new Java("org.apache.jasper.runtime.JspFactoryImpl");
$pageContext = $factory->getPageContext($this->servlet,
      $this->request, $this->response, null, true, 1892, false);
$tag = new Java("com.c4u.eis.core.view.tags.content.InitpageTag");
$tag->setPageContext($pageContext);
$tag->doStartTag();
$tag->doEndTag();

?>

Und das funktioniert! Mit einem Haken: wir würden keine Ausgabe erhalten. Dazu müssen wir noch explizit die flush()-Methode des out-Objektes aufrufen. Wir erhalten es vom PageContext:

<doStartTag();
$tag->doEndTag();
$pageContext->getOut()->flush();
?>

Neue Probleme...

Das war der einfache Teil der Geschichte. Den mit dieser simplen Logik können Sie nur Tags der Form <abc:xyz /> verwenden. Bei <abc:xyz> </abc:xyz> bzw. verschachtelten Tags, wie auszugsweise hier:

<content:component_loop template="c4u_tdi_destination">
  <layout:tablecell>
    <layout:component_output component="componentname" />
  </layout:tablecell>
</content:component_loop>

funktioniert der Ansatz zwar prinzipiell auch, aber die Stolperfallen sind wesentlich heimtückischer. Vor allem muss man sich klar sein, dass das Tag <content:component_loop> hier die Funktion einer Schleife besitzt.

PHP-Klassen für JSP-Tags

Um uns nicht weiter in den Details der Tomcat-Programmierung zu verlieren, stelle ich Ihnen eine Reihe von PHP-Klassen vor, die den Umgang mit JSP-Klassen zu vereinfachen.

Das ZIP-Archiv mit den hier genannten PHP-Klassen finden Sie unter http://www.alexander-merz.com/tomcatphp.zip.

Den Anfang macht Java_Tomcat_PageContext. Diese Klasse ist ein Wrapper für die Erzeugung des PageContext-Objektes von Tomcat. Die Verwendung ist recht einfach:

<?php
require_once "Java/Tomcat/PageContext.php";
$pc = new Java_Tomcat_PageContext($servlet, $request, $response);
?>

Alle weiteren hier vorgestellten PHP-Klassen erwarten als Parameter ein Objekt dieser Klasse. Das originale Java-PageContext-Objekt erhalten Sie mit $pc->getPageContext().

Die wichtigste Klasse ist Java_Tag. Damit erzeugen Sie ein Objekt, mit dem Sie einfach das Tag benutzen können. Der Konstruktor der Klasse erwartet das obige PageContext-Objekt, den Namen der Java-Tag-Klasse und, optional, die Attribute des Tags. Nehmen wir folgenden Tag-Arufruf:

<core:text id="c4u.wbt.course.showcourse.targetgroup"/>

Mit Java_Tag wird dieser in PHP zu:

<?php
require_once "Java/Tag.php";
$attribute = array("id" => "c4u.wbt.course.showcourse.targetgroup");
$textTag = new JavaTag($pc, "c4u.core.view.tags.TextTag", $attribute);
$textTag->call();
?>

Die Methode führt den eigentlichen Tag-Aufruf durch. Standardmäßig wird die Ausgabe des Tags sofort ausgegeben. Übergeben Sie an call() den Parameter false, dann müssen Sie sich selbst darum kümmern:

<?php
…
$textTag->call(false);
$pc->getOut()->flush();
?>

Im folgenden Fall ist es aber nicht so einfach:

<core:sqlloop sqlobject="p_theme">
 …
</core:sqlloop>

Denn dieses Konstrukt kann auch eine Schleifenkonstruktion darstellen – oder überhaupt nicht durchlaufen werden.

Deshalb ist in diesem Fall die Logik etwas komplexer. Mit Java_Tag sieht das ganze so aus:

<?php
$attribute = array( "sqlobject"=>"p_theme");
$sqlloopTag = new Java_Tag($pc, "c4u.core.view.tags.SQLLoopTag", $attribute);
if($sqlloopTag->start()) {
  do {
   …
  } while($sqlloopTag->repeat());
  $sqlloopTag->end();
}
?>

Spezialfall

Mit der eher einfachen Java_Tag-Klasse können Sie bereits viele Standardsituationen handhaben. Sollten es doch notwendig werden, von Hand ein Tag aufzurufen, dann werden Sie u.U. auf ein Problem stoßen.

Die Ausgabe, die von den Tags generiert wird, erfolgt über das Tomcat-PageContext-Objekt, und damit an PHP vorbei. Das kann dazu führen, dass Ausgaben an Stellen erfolgen, wo sie eigentlich nicht auftreten sollen. In solchen Fällen müsste die Ausgabe irgendwie gepuffert werden.

Die Lösung dafür ist die Klasse Java_Tag_Buffer. Sie erzeugen ein Objekt dieser Klasse und kleiden die Tag-Klassenaufrufe zwischen zwei Methoden der Buffer-Klasse, und können sich die gespeicherte Ausgabe später holen:

<?php
require_once "Java/Tag/Buffer.php";

$buffer = new Java_Tag_Buffer($pc);
$buffer->start();
… // Tag-Aufrufe
$buffer->close();

echo $buffer->getString();
?>

Damit diese Klasse korrekt funktioniert, muss das Verzeichnis Java/Tag/com mit der Klasse OutputBufferTag im Archiv kopiert werden in das Verzeichnis WEB-INF/classes des Context-Roots, in dem sich Ihre PHP-Skripte befinden.

Dieser Artikel war sehr lang und ich hoffe, dass Sie trotzdem bis zum Schluss durchgehalten haben. In der nächsten Woche werde ich diese Artikelserie zum Abschluss bringen und betrachten Performance und Stabilität der Referenz-Implementierung.

1. Teil: Tomcat&PHP - Installation
2. Teil: Tomcat&PHP - Wie funktioniert es
3. Teil: Tomcat&PHP - Vordefinierte Variablen
5. Teil: Tomcat&PHP - Performance