Русский English Тэги View Sergey Zolotaryov's profile on LinkedIn Вход
Как заставить Tiles2 не прятать ошибки рэндеринга
Постоянная ссылка 06-03-2008 anydoby java

Недавно обнаружилось, что пользоваться Tiles2 не так уж и удобно, особенно если ваиш POJO бросают ошибки при рэндеринге. Понимаю, что на этапе отображения уже не должно быть ошибок, но все-таки бывает. Например, вы используете какие-нибудь умные ленивые POJO. При бросании ошибки, она просто пишется tiles2 в лог, а на месте ошибочного фрагмента просто пустота. Как с этим бороться?

Слава богу, что фреймворк открытый и можно в нем покопаться :)

Вот как я решил проблему в Spring MVC.

Для начала нужно создать свой TilesContextFactory. Так как я пользуюсь JSP, логичнее всего будет расширить JspTilesContextFactory:


package com.anydoby.web;


import java.io.IOException;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.ServletRequest;
import javax.servlet.jsp.PageContext;

import org.apache.tiles.TilesApplicationContext;
import org.apache.tiles.context.TilesRequestContext;
import org.apache.tiles.jsp.context.JspTilesContextFactory;
import org.apache.tiles.jsp.context.JspTilesRequestContext;
import org.apache.tiles.jsp.context.JspUtil;

/**
 * This context factory will delegate execution to the {@link JspTilesContextFactory} except for
 * creation of request context. The request context this factory generates is able to handle
 * exceptions by forwarding an exception to a pre-configured web page for rendering.
 * 
 * @author SergeyZ
 * 
 */
public class ExceptionExposingContextFactory extends JspTilesContextFactory {

  private static final String ERROR_PAGE_PATH = "errorPagePath";
  private String errorPage;


  @Override
  public void init(Map<String, String> configParameters) {
    super.init(configParameters);
    errorPage = configParameters.get(ERROR_PAGE_PATH);
  }


  @Override
  public TilesRequestContext createRequestContext(TilesApplicationContext context,
      Object... requestItems) {
    if (requestItems.length == 1) {
      ServletContext servletContext = getServletContext(context);
      if (servletContext != null) {
        final PageContext ctx = (PageContext)requestItems[0];
        return new JspTilesRequestContext(servletContext, ctx) {
          @Override
          public void include(String path) throws IOException {
            try {
              JspUtil.doInclude(ctx, path, false);
            } catch (Exception e) {
              ServletRequest rq = ctx.getRequest();
              rq.setAttribute("exception", e);
              super.include(errorPage);
              rq.removeAttribute("exception");
            }
          }
        };
      }
    }
    return null;
  }
}

Этого было бы достаточно, если бы init вызывался Tiles при создании новой фабрики. На практике фабрики в Tiles2 только инстанциируются, а не инициализируются. Стыд и срам создателям за такое упущение :(

Ничего не стоит создать свой инициализатор:


package com.anydoby.web;


import java.lang.reflect.Field;
import java.util.Map;

import org.apache.tiles.context.ChainedTilesContextFactory;
import org.apache.tiles.context.TilesContextFactory;

/**
 * This context factory will initialize the factories which it created using the
 * {@link TilesContextFactory#init(java.util.Map)}.
 * @author SergeyZ
 * 
 */
public class InitalizingChainedContextFactory extends ChainedTilesContextFactory {

  @Override
  public void init(Map<String, String> configParameters) {
    super.init(configParameters);
    try {
      Field field = getClass().getSuperclass().getDeclaredField("factories");
      field.setAccessible(true);
      TilesContextFactory[] factories = (TilesContextFactory[])field.get(this);
      field.setAccessible(false);
      for (TilesContextFactory tilesContextFactory : factories) {
        tilesContextFactory.init(configParameters);
      }
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

}

К сожалению, встроенный в Tiles ChainedTilesContextFactory не только не инициализирует фабрики, но еще и плохо расширяется, поэтому пришлось прибегнуть к грязному хаку - доступ к полю с фабрикой по рефлекции.

Ну а теперь - самое простое - конфигурация всей кухни со Spring:


    <bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
        <property name="definitions">
            <list>
                <value>/WEB-INF/config/tiles-defs.xml</value>
            </list>
        </property>
        <property name="useMutableTilesContainer" value="true"></property>
        <property name="tilesProperties">
            <props>
                <prop key="org.apache.tiles.context.TilesContextFactory">
                    com.anydoby.web.InitalizingChainedContextFactory
                </prop>
                <prop key="org.apache.tiles.context.ChainTilesContextFactory.FACTORY_CLASS_NAMES">
                    org.apache.tiles.servlet.context.ServletTilesContextFactory,
                    com.anydoby.web.ExceptionExposingContextFactory
                </prop>
                <prop key="errorPagePath">/WEB-INF/views/error.jsp</prop>
            </props>
        </property>
    </bean>

Страница с сообщением об ошибке спрятана тут WEB-INF/views.

Добавить комментарий

Предыдущая статья Ляпы EJB Следующая статья Как я получал свои Java сертификаты