Немного механики работы UrlRewriteFilter, который часто используется джаверами при создании приложений. И не только servlet-based, но еще и использующих портлеты. Исследовался конкретно JBoss portal 2.6.5, но всё это на 99% можно отнести к другим серверам приложений и сервлет-контейнерам.
1. Сначала исходный запрос (CoyoteRequest) обрабатывается томкатом, выделяя параметры, которые присутствуют в запросе. Допустим, запрос был такого вида:
http://localhost:8080/wiki/l/А?d=s&mmm=asd
Так вот, параметры d и mmm будут присутствовать в коллекции parameters вне зависимости от применённых маппингов.
2. После этого от запроса отрезаются параметры, и полученный URL попадает в фильтр URLRewrite.
Точнее, никто их не отрезает, естественно, но фильтр URLRewrite их попросту не учитывает.
Таким образом, на вход UrlRewrite filter поступает урл
http://localhost:8080/wiki/l/А
3. Найдя подходящий маппинг, UrlRewriteFilter применяет его к запросу, передавая в filterChain измененный запрос.
Следующие фильтры и итоговые сервлеты, а далее и портлет-контейнер, а потом и собственно портлеты увидят преобразованный запрос.
http://localhost:8080/wiki/?l=A
И тут мы видим, что первичные параметры d и mmm казалось бы утеряны, но не тут-то было ! При реврайтинге запроса UrlRewriteFilter сохранил их. Скорее всего, при инициализации замещающего запроса туда были сразу скопированы параметры исходного запроса, а потом уже дописаны те, которые получились при парсинге отреврайтенного запроса.
Для того, чтобы окончательно убедиться в правильности этих мыслей, необходимо посмотреть исходный код UrlRewriteFilter. Но пока, к сожалению, на это нет времени.
Заметьте, UrlRewriteFilter мапит только URL, а не целиком весь запрос с параметрами. Поэтому, если вам вдруг захочется сделать маппинг, который бы зависел от параметров, то у вас это не получится (предупреждаю, потому что сам как-то с этим столкнулся). UrlRewriteFilter будет попросту игнорировать параметры, поскольку на входе имеет только url. Но такая фича и не нужна, поскольку параметры, дописанные к реврайченным урлам, остаются в запросе, и вы можете попросту добавить логику обработки этих параметров в приложение.
На днях вспомнил об одной задаче, которая мне попалась на работе года полтора назад.
В то время я еще не знал об sql-ex.ru и мои познания в SQL были достаточно слабыми, на уровне
простейших селектов с inner join’ами. И, как я помню тогда, я не смог решить эту задачу без
дополнительного подзапроса. Сейчас, после нескольких десятков решенных задач на sql-ex.ru я
без проблем с ней справился, что навело меня на мысль о том, что это – неплохой тест на знание
азов SQL. В отличие от более сложных задач, эта задачка не требует большого количества времени, но
покрывает почти всё, что требуется для среднего программера в вопросе знания SQL, особенно если решать её в уме. Сразу отпадают вопросы наподобие “что такое JOIN, чем LEFT JOIN отличается от INNER JOIN, как работает группировка” итд.
Итак, формулировка такова. Есть 2 таблицы – Collection и Item.
Необходимо сделать выборку
Collection.ID Collection.Name Collection.Count
тех коллекций, которые содержат меньше 5 элементов.
Вот и всё.
ДАЛЬШЕ СПОЙЛЕР, НЕ ЧИТАТЬ !!!111 (Я не научился пока скрывать в вордпрессе куски постов, но как только научусь – оформлю соответствующе).
Алгоритм решения задачи.
Ну ясно же – надо сделать JOIN, делаем JOIN, группируем по Collection.ID, выводим ID, Name, Count(*).
Черт. При группировке по ID нельзя вывести Name. Значит, надо либо завернуть полученную выборку в еще 1 запрос, либо дописать в группировку этот Name. Ок, работает. Только пустые коллекции не выводятся. Почему ? Ну ясно же – они не попадают в результат INNER JOIN’a, надо использовать LEFT JOIN. О, теперь получилось. Только коллекции-то пустые, а count(*) выводит единицу вместо нуля. Ах да, это же LEFT JOIN, он джойнит Collection.ID Collection.Name NULL NULL и count(*) дает единицу. Как поправить-то. Вычесть 1 нельзя, поскольку так можно запороть непустые коллекции. Надо как-то проверить, является ли коллекция пустой, и для нее вывести 0, а для остальных – count(*). Да у нас же справа NULL, можно их в какую-нибудь агрегатную функцию запихнуть, и сравнить с NULL’ом. Если NULL, значит, коллекция пустая, и для нее выводим 0, иначе – count(*). Эврика! Ну и добавляем HAVING. Пишем запрос:
SELECT c.ID, c.Name, CASE WHEN COUNT(*) = 1 AND MAX(i.CollectionID) IS NULL THEN 0 ELSE COUNT(*) END FROM Collection c LEFT JOIN Item i ON c.ID = i.CollectionID GROUP BY c.ID, c.Name HAVING COUNT(*) < 5 |
Если вам нужно что-то постоянно передеплоить, а то и перезапускать, и вам надоело ждать по 3 минуты, когда стартует JBoss Portal, не отчаивайтесь ! Используйте JRebel.
Эта штука реализует подход, более продвинутый, чем HotSwap, и можно не только менять код существующих методов в классах, но и дописывать новые, добавлять филды (в т.ч. статические), конструкторы итд. Для сервлет-контейнеров и app серверов есть специальные приблуды типа корректной обработки релоада не только джаваклассов, но и jsp, Hibernate proxy, в общем много чего.
Последнюю версию с таблэткой можно найти на том же rutracker.
bin/jrebel-app-config.cmd поможет вам настроить Вашу любимую IDE. Только учтите, что при создании run_jrebel.bat вам, скорее всего, нужно будет запускать не run.bat а run_jpda.bat (вы же хотите отлаживать свой код, не так ли?).
Единственный вопрос, остающийся после визарда – что должен содержать rebel.xml и вообще зачем он нужен ? Этот файл должен быть внутри деплоящегося модуля (jar, war) и должен содержать пути к классам, которые могут обновляться. JRebel при первой загрузке модуля смотрит его и начинает мониторить указанные директории. Если классы/jsp в них обновляются (по timestamp), он их перезагружает из этих директорий. В jar этот xml файл должен лежать в корне. Таким образом,
portal-core-lib.jar
> META-INF
> classes
> > some stuff
> rebel.xml
А в rebel.xml вот такая борода:
<?xml version="1.0" encoding="UTF-8" ?> <application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.zeroturnaround.com" xsi:schemaLocation="http://www.zeroturnaround.com http://www.zeroturnaround.com/alderaan/rebel-2_0.xsd"> <classpath> <dir name="C:/elwood/work/portal/core/out/production/portal-core-lib" /> </classpath> </application> |
HotSwap в IDEA можно (и даже нужно, скорее всего, поскольку он не будет работать корректно) отключить. Классы помечаются для релоада после их перекомпиляции посредством Ctrl-Shift-F9 (compile one class).
Релоадятся классы не сразу, а при доступе приложения к ним.
7