вторник, 28 июля 2009 г.

XSLT & Google App Engine/Java

Поставил целью запустить простейший XSLT-фреймворк на GAE/J. Требования к процессору:

  1. XSLT 1.0 в полном объеме. А неплохо бы и 2.0 на всякий случай;
  2. Скорость;
  3. Поддержка EXSLT. Хотя-бы частичная, но node-set обязательно.
  4. Расширяемость внешними Java-функциями. Возможность вызова методов класса;
  5. Лицензия на коммерческое применение;
  6. Желательна бесплатность и исходные тексты;
  7. Желательна поддержка TrAX - transformation API for XML.

Вначале долго искал хоть какие результаты тестов производительности. Все найденные данные либо безбожно устарели, либо составлены так, что понять что-либо абсолютно невозможно. Любимый прием авторов сравнений – утаить подробности. Например, пишут “Saxon X.X.X.X”. Что за Saxon? Saxon-B или Saxon-SA? Для дотнета или для явы? И так везде. В общем путем долгих мучений выяснилось что известными более-менее быстрыми процессорами, написанными на яве, являются Gregor, Xalan/XSLTC, XT, Saxon 6.5/Saxon-B/Saxon-SA.

Gregor декларировался как фантастически быстрый компилятор нового поколения. На деле оказался заброшенным коммерческим проектом, абсолютно недоступным для отдельного скачивания. Жаль.

Xalan/XSLTC. Входит в JDK. Правда какой-то древней версии. Попробовал использовать свежий xalan-j 2.7.1. Сразу выяснилось, что по сути XSLTC не поддерживает внешние нестатические функции, да и без их использования валится на GAE с криками “Illegal type in constant pool”. Без использования режима компиляции xalan на GAE работоспособен, но загаживает лог криками “Failed calling setMethod method” и “Failed calling setIndent method”. Т.е. не понимает тег <xsl:output>. Да и самый медленный он из всех перечисленных. Короче – в сад.

XT-20051206. По отзывам – очень быстр. Похоже, что проект заброшен. Не смог обработать мой шаблон даже локально, до GAE не дошел. В брак.

Saxon-SA это коммерческий продукт. Значит и пробовать нужно когда проект работает под нагрузкой и приносит прибыль. Не проверял. Но похоже его и не нужно как-то особо проверять ибо полностью внешне совместим с Saxon-B.

Saxon 6.5.5 сейчас подзаброшен. Не проверял, т.к. каких-либо преимуществ перед Saxon-B не имеет, скорее наоборот.

Saxon-B 9.1.0.7 j. Завелся с полпинка как локально, так и на GAE. Оказался быстрее xalan’а на моем простом примере. Работает стабильно. На нем и остановился.

Единственный замеченный глюк – в некоторых случаях xml-файлы могут самопроизвольно искаться в каталоге “/base/” (и не находиться, естественно). Чтобы этого не происходило, нужно использовать классы FileInputStream и File. В них с обработкой путей всё в порядке.

В итоге получился краткий и лаконичный код. Ключевые моменты выделены жирным.

public class XstlServlet
  extends HttpServlet
{
  @Override
  public void doGet(final HttpServletRequest req,
    final HttpServletResponse resp)
    throws IOException, ServletException
  {
    request = req;
    resp.setContentType("text/html; charset=UTF-8");

    try { 
      getTransformer("default").transform(
        new StreamSource(new FileInputStream("default.xml")),
        new StreamResult(resp.getOutputStream()));
    } catch (TransformerException e) {
      throw new ServletException(e);
    }
  }

  public Transformer getTransformer(final String xsltName)
    throws FileNotFoundException, TransformerException,
      MalformedURLException
 
{
    if (!transformers.containsKey(xsltName)) {
     
final Transformer newXslt = xsltFactory.newTransformer(
       
new StreamSource(new FileInputStream(xsltName + ".xslt")));
      newXslt.setParameter("this", this);
     
transformers.put(xsltName, newXslt);
   
}

    return transformers.get(xsltName);
  }

  //
  // callback methods for xslt transformation
  //

  public static String getServletPath(final Object context)
 
{
   
return ((XstlServlet) context).request.getServletPath();
  }

 
public static String getParameter(final Object context,
    final String paramName)

 
{
   
return ((XstlServlet) context).request.getParameter(paramName);
 
}

 
//
 
// private declarations
 
//

 
private HttpServletRequest request;
 
private final Map<String, Transformer> transformers =
   
new Hashtable<String, Transformer>();
 
private final static TransformerFactory xsltFactory =
    TransformerFactory.newInstance();
}

В xslt-шаблоне для вызова внешних методов применяется классический прием – объявление специального пространства имен, ссылка на наш объект через параметр в корневом узле и передача ссылки на объект как первый параметр вызываемой функции.

<xsl:stylesheet version="1.0"
xmlns:xsl=”http://www.w3.org/1999/XSL/Transform” 
xmlns:this=”http://xml.apache.org/xalan/java/com.test.XstlServlet”
exclude-result-prefixes="this">

 
<xsl:param name="this" />

  <xsl:template match="/">
    <xsl:value-of select="this:getParameter($this, @параметр)" />
    <xsl:value-of select="this:getServletPath($this)" />

  </xsl:template>

</xsl:stylesheet>

Осталось еще кеширование нормальное и многопоточность добавить - и фундамент готов.

UPDATE: Статья обновлена. В настоящее время GAE не имеет своего xslt-процессора. Это моя случайная ошибка – в каталоге lib был забыт saxon9.jar, чего оказалось достаточно чтобы гугл его подхватил.

1 коммент.:

fivig комментирует...

Я с помощью htmpparser on java на GAE запустил грабер работает прекрасно, только столкнулся с проблемой получения количества записей в базе когда их колличество подошло к 1000