При переносе JSF-портлетов с Liferay 6.2 на Liferay 7.1 обнаружилось неприятное поведение - далеко не все исключения, происходящие при работе при работе приложения, фиксируются в логах. Некоторые из них отображаются на странице, другие не отображаются вообще нигде. Были обнаружены следующие ситуации:
@PostConstruct методы
- исключение отображается только на странице.
- action'ы у не-AJAX компонентов - исключение не отображается вообще.
- Исключения при рендере ответа (например, EL-ValueExpression бросило исключение) - ошибки отображаются на странице.
@PostConstruct
import javax.annotation.PostConstruct;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class InitFailureBean {
@PostConstruct
private void init() {
throw new RuntimeException("HELLO THERE!");
}
public String getProp() {
return "Hello, cruel world!";
}
}
view.xhtml
<f:view
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
>
<h:head/>
<h:body>
<h:outputText value="#{initFailureBean.prop}"/>
</h:body>
</f:view>
CommandButton action
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class AjaxFailureBean {
public static final Log log = LogFactoryUtil.getLog(AjaxFailureBean.class);
public void actionFailure() {
log.info("There will be an error");
if (true) {
throw new RuntimeException("Invisible error!");
}
log.info("After exception");
}
}
<f:view
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
>
<h:body>
<h:form>
<p:commandButton value="Action failure!" ajax="false" action="#{ajaxFailureBean.actionFailure}" update="sampleOutput"/>
</h:form>
</h:body>
</f:view>
Страница после нажатия кнопки
Содержимое лога после трёх нажатий кнопки
Исключение при рендере
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;
@ManagedBean
@ViewScoped
public class RenderFailureBean {
public static final Log log = LogFactoryUtil.getLog(RenderFailureBean.class);
public String getFailureProp() {
log.info("Before exception");
if (true) {
throw new RuntimeException("Exception here!");
}
log.info("After exception");
return "Hello, cruel world!";
}
}
<f:view
xmlns="http://www.w3.org/1999/xhtml"
xmlns:f="http://xmlns.jcp.org/jsf/core"
xmlns:h="http://xmlns.jcp.org/jsf/html"
xmlns:p="http://primefaces.org/ui"
xmlns:ui="http://java.sun.com/jsf/facelets"
>
<h:body>
<h:form>
<h:outputText value="#{renderFailureBean.failureProp}"/>
</h:form>
</h:body>
</f:view>
Страница после ошибки:
Логи после ошибки:
Решение проблемы
Для решения этой проблемы был написан простой обработчик исключений JSF, добавляющий сообщения об ошибке в логи.
import com.liferay.portal.kernel.log.Log;
import com.liferay.portal.kernel.log.LogFactoryUtil;
import javax.faces.FacesException;
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerWrapper;
import javax.faces.event.ExceptionQueuedEvent;
public class LiferayJsfExceptionLogger extends ExceptionHandlerWrapper {
private static final Log log = LogFactoryUtil.getLog(LiferayJsfExceptionLogger.class);
private ExceptionHandler wrapped;
public LiferayJsfExceptionLogger(ExceptionHandler wrapped) {
this.wrapped = wrapped;
}
@Override
public ExceptionHandler getWrapped() {
return wrapped;
}
@Override
public void handle() throws FacesException {
for (ExceptionQueuedEvent event : getUnhandledExceptionQueuedEvents()) {
Throwable throwable = event.getContext().getException();
log.error(throwable);
}
super.handle();
}
}
import javax.faces.context.ExceptionHandler;
import javax.faces.context.ExceptionHandlerFactory;
public class LiferayJsfExceptionLoggerFactory extends ExceptionHandlerFactory {
private final ExceptionHandlerFactory parent;
public LiferayJsfExceptionLoggerFactory(ExceptionHandlerFactory parent) {
this.parent = parent;
}
@Override
public ExceptionHandler getExceptionHandler() {
return new LiferayJsfExceptionLogger(parent.getExceptionHandler());
}
}
<?xml version="1.0"?>
<faces-config
version="2.2"
xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"
>
<factory>
<exception-handler-factory>com.tuneit.ibox.jsf.LiferayJsfExceptionLoggerFactory</exception-handler-factory>
</factory>
</faces-config>
Кроме того, всё это можно оформить в виде отдельной библиотеки и подключать её, как зависимость - тогда отпадёт потребность в ручной регистрации фабрики в faces-config.xml.
Проверяем предыдущие примеры (например, с ошибкой при рендере).
Страница:
Логи:
Засим откланиваюсь, прощайте.