py.una.pol.karaku.util.ControllerHelper.java Source code

Java tutorial

Introduction

Here is the source code for py.una.pol.karaku.util.ControllerHelper.java

Source

/*-
 * Copyright (c)
 *
 *       2012-2014, Facultad Politcnica, Universidad Nacional de Asuncin.
 *       2012-2014, Facultad de Ciencias Mdicas, Universidad Nacional de Asuncin.
 *       2012-2013, Centro Nacional de Computacin, Universidad Nacional de Asuncin.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301  USA
 */

package py.una.pol.karaku.util;

import static py.una.pol.karaku.util.Checker.notNull;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.TimeZone;
import javax.annotation.Nonnull;
import javax.el.ELContext;
import javax.el.ExpressionFactory;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.FacesException;
import javax.faces.application.FacesMessage;
import javax.faces.application.FacesMessage.Severity;
import javax.faces.component.UIComponent;
import javax.faces.component.UIComponentBase;
import javax.faces.component.UIInput;
import javax.faces.component.UISelectOne;
import javax.faces.component.UIViewRoot;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.context.FacesContext;
import javax.faces.convert.DateTimeConverter;
import org.hibernate.exception.ConstraintViolationException;
import org.richfaces.component.UICalendar;
import org.richfaces.component.UIColumn;
import org.richfaces.component.UIExtendedDataTable;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import py.una.pol.karaku.math.Quantity;
import py.una.pol.karaku.reports.Column;
import com.google.common.annotations.VisibleForTesting;

/**
 * Clase que implementa funcionalidades generales para la manipulacion de
 * vistas, proveen funcionalidades que ya integran todas las partes del sistema.
 * Es un singleton compartido por todas las sesiones
 * 
 * @author Arturo Volpe
 * @author Nathalia Ochoa
 * @since 2.0 08/02/2013
 * @version 2.0 19/02/2013
 */
@Component
public class ControllerHelper {

    private static final String NULL_SEVERITY_IS_NOT_ALLOWED = "Null severity is not allowed";
    private static final String EMPTY_STRING = "";
    private static final String EL_VALUE_PROPERTY = "value";

    @Autowired
    private UniqueHelper uniqueHelper;

    @Autowired
    private I18nHelper i18nHelper;

    /**
     * Crea un mensaje global, que ser mostrado en lugares como
     * <rich:messages ajaxRendered="true" showDetail="true"
     * globalOnly="true"/>
     * <p>
     * El parmetro GlobalOnly permite que solo se muestren este tipo de
     * mensajes, en caso contrario se mostrarn todos los mensajes
     * 
     * </p>
     * 
     * @param severity
     *            grado de severidad {@link FacesMessage}
     * @param summary
     *            resumen
     * @param detail
     *            detalle del mensaje
     */
    public void createGlobalFacesMessage(final Severity severity, @Nonnull final String summary,
            @Nonnull final String detail) {

        createGlobalSimpleMessage(severity, getString(notNull(summary)), getString(notNull(detail)));

    }

    /**
     * Crea un mensaje global, que ser mostrado en lugares como
     * &lt;rich:messages ajaxRendered="true" showDetail="true"
     * globalOnly="true">
     * <p>
     * El parmetro GlobalOnly permite que solo se muestren este tipo de
     * mensajes, en caso contrario se mostraran todos los mensajes
     * </p>
     * 
     * @param severity
     *            grado de severidad {@link FacesMessage}
     * @param summary
     *            clave de lmensaje de internacionalizacin
     */
    public void createGlobalFacesMessage(final Severity severity, final String summary) {

        createGlobalSimpleMessage(severity, getString(summary), null);
    }

    /**
     * Crea un mensaje global, que ser mostrado en lugares como
     * &lt;rich:messages ajaxRendered="true" showDetail="true"
     * globalOnly="true">
     * <p>
     * El parmetro GlobalOnly permite que solo se muestren este tipo de
     * mensajes, en caso contrario se mostraran todos los mensajes
     * </p>
     * 
     * @param severity
     *            grado de severidad {@link FacesMessage}
     * @param summary
     *            mensaje
     */
    public void createGlobalFacesMessageSimple(final Severity severity, final String summary) {

        createGlobalSimpleMessage(severity, summary, null);
    }

    /**
     * Crea un mensaje global, que ser mostrado en lugares como
     * &lt;rich:messages ajaxRendered="true" showDetail="true"
     * globalOnly="true">
     * <p>
     * El parmetro GlobalOnly permite que solo se muestren este tipo de
     * mensajes, en caso contrario se mostraran todos los mensajes
     * </p>
     * 
     * @param severity
     *            grado de severidad {@link FacesMessage}
     * @param summary
     *            mensaje
     */
    public void createGlobalSimpleMessage(@Nonnull final Severity severity, final String summary,
            final String detail) {

        notNull(severity, NULL_SEVERITY_IS_NOT_ALLOWED);

        String sum = summary == null ? EMPTY_STRING : summary;
        String det = detail == null ? EMPTY_STRING : detail;

        createFacesMessageSimple(severity, sum, det, null);
    }

    /**
     * Retorna el mensaje internacionalizado del cdigo dado, para claves no
     * encontradas retorna &&&&&code&&&&&&
     * 
     * @param code
     *            llave del mensaje
     * @deprecated utilizar el {@link I18nHelper}
     * @return cadena internacionalizada
     */
    @Deprecated
    public String getMessage(final String code) {

        return i18nHelper.getString(code);
    }

    /**
     * Crea un mensaje para un componente determinado
     * 
     * @param severity
     *            Severidad {@link FacesMessage}
     * @param summary
     *            Clave internacionalizada del sumario
     * @param detail
     *            Clave internacionalizada del detalle
     * @param componentId
     *            Nombre del componente,
     *            {@link ControllerHelper#getClientId(String)}
     */
    public void createFacesMessage(final Severity severity, final String summary, final String detail,
            final String componentId) {

        createFacesMessageSimple(severity, getString(summary), getString(detail), componentId);
    }

    /**
     * Crea un mensaje  personalizado para un componente determinado utilizando
     * las claves  y los argumentos recibidos como parametros.
     *
     * @see I18nHelper#getString(String, Object...)
     *
     * @param severity
     *            Severidad {@link FacesMessage}
     * @param summary
     *            Clave internacionalizada del sumario
     * @param summaryArgs
     *            Argumentos para personalizar el sumario
     * @param detail
     *            Clave internacionalizada del detalle
     * @param detailArgs
     *            Argumentos para personalizar el detalle
     * @param componentId
     *            Nombre del componente,
     *            {@link ControllerHelper#getClientId(String)}
     */
    public void createFacesMessage(final Severity severity, final String summary, Object[] summaryArgs,
            final String detail, final Object[] detailArgs, final String componentId) {

        createFacesMessageSimple(severity, getString(summary, summaryArgs), getString(detail, detailArgs),
                componentId);
    }

    /**
     * Emite un mensaje recibido como parmetro en el componente cuyo
     * identificador es recibido como parmetro.
     * 
     * <p>
     * Desde la version
     * 
     * @param severity
     *            Severidad {@link FacesMessage}
     * @param summary
     *            Clave internacionalizada del sumario
     * @param detail
     *            Clave internacionalizada del detalle
     * @param componentId
     *            Nombre del componente,
     *            {@link ControllerHelper#getClientId(String)}
     */
    public void createFacesMessageSimple(@Nonnull final Severity severity, final String summary,
            final String detail, final String componentId) {

        notNull(severity, NULL_SEVERITY_IS_NOT_ALLOWED);

        addMessage(severity, summary, detail, componentId);
    }

    /**
     * Agrega un mensaje con severidad <b>info</b> e internacionalizado.
     * 
     * @param summary
     *            cadena del archivo de internacionalizacin del sumario del
     *            mensaje
     * @param params
     *            parametros del mensaje.
     */
    public void addInfoMessage(@Nonnull final String summary, final Object... params) {

        addMessage(FacesMessage.SEVERITY_INFO, i18nHelper.getString(summary, params), null, null);
    }

    /**
     * Agrega un mensaje con severidad <b>info</b> e internacionalizado.
     * 
     * @param summary
     *            cadena del archivo de internacionalizacin del sumario del
     *            mensaje
     * @param params
     *            parametros del mensaje.
     */
    public void addWarnMessage(@Nonnull final String summary, final Object... params) {

        addMessage(FacesMessage.SEVERITY_WARN, i18nHelper.getString(summary, params), null, null);
    }

    /**
     * Agrega un mensaje con severidad warn a un componente.
     * 
     * @see I18nHelper#getString(String, Object...)
     * @param id
     *            identificador del componente (puede ser id de cliente o no).
     * @param summary
     *            sumario del mensaje
     * @param detail
     *            detalle
     */
    public void addWarnMessage(@Nonnull final String id, @Nonnull final String summary, final String detail) {

        addMessage(FacesMessage.SEVERITY_WARN, summary, detail, id);
    }

    /**
     * Agrega un mensaje con severidad <b>warn</b> e internacionalizado
     * 
     * @param summary
     * @param params
     */
    public void addGlobalWarnMessage(@Nonnull final String summary, final String detail) {

        addMessage(FacesMessage.SEVERITY_WARN, summary, detail, null);
    }

    /**
     * Agrega un mensaje con severidad <b>warn</b>.
     * 
     * <p>
     * Las cadenas pasadas ya deben estar internacionalizadas
     * </p>
     * 
     * @param summary
     * @param params
     */
    public void addSimpleGlobalWarnMessage(@Nonnull final String summary, final String detail) {

        addMessage(FacesMessage.SEVERITY_WARN, summary, detail, null);
    }

    /**
     * Agrega un nuevo mensaje, busca el ID si el mismo no es un id compuesto.
     * 
     * <h3>Consideraciones:</h3>
     * <p>
     * <b>El id ya puede ser del cliente</b> en este caso se realiza una
     * verifiacion si posee el separador <code>:</code>, si es as entonces
     * utiliza directamente el parmetro. En el caso de que sea un client id y
     * no tenga <code>:</code> se realiza una bsqueda innecesaria.
     * </p>
     * <p>
     * <b>Si el id es <code>null</code></b> entonces se crea un mensaje global
     * </p>
     * 
     * @param severity
     * @param summary
     * @param detail
     * @param id
     * @since 2.0
     */
    private void addMessage(FacesMessage.Severity severity, String summary, String detail, String id) {

        String realId = id;
        if (realId != null && realId.indexOf(':') == -1) {
            realId = getClientId(id);
        }
        getContext().addMessage(realId, new FacesMessage(severity, summary, detail));
    }

    /**
     * Returns the clientId for a component with id, esto se usa por que JSF
     * genera claves distintas a las que se configuran en los componentes, esto
     * se hace para evitar que el mismo ID se repite, por ejemplo si ponemos en
     * un form un label, con formID y labelID como sus IDs respectivamente, JSF
     * generara identificadorse parecidos a: formID para el form, y
     * formID:labelID para el label, esta funcion recibe como parametro
     * "labelID" y retorna "formID:labelID".
     * 
     * @param id
     *            de la vista del elemento a buscar
     * @return id del componente del lado del cliente
     */
    public String getClientId(final String id) {

        UIComponent c = findComponent(id);
        if (c == null) {
            throw new IllegalArgumentException("NO se encontro comoponente con id " + id);
        }
        return c.getClientId(getContext());
    }

    /**
     * Dado un ID (vease {@link ControllerHelper#getClientId(String)}) retorna
     * el componente al que pertenece
     * 
     * @param id
     *            id del cliente para obtener el componente
     * @return Componente de vista
     */
    public UIComponent findComponent(final String id) {

        FacesContext context = getContext();
        UIViewRoot root = context.getViewRoot();

        return findComponent(root, id);
    }

    /**
     * Retorna una EL expression correspondiente a un metodo.
     * 
     * @param valueExpression
     *            cadena que representa la expresion.
     * @param expectedReturnType
     *            clase del tipo que se espera que retorna la expresion
     * @param expectedParamTypes
     *            clase de los parametros esperados que reciba el metodo
     * 
     * @return {@link MethodExpression} correspondiente
     */
    public MethodExpression createMethodExpression(final String valueExpression, final Class<?> expectedReturnType,
            final Class<?>... expectedParamTypes) {

        MethodExpression methodExpression = null;
        try {
            FacesContext fc = getContext();
            ExpressionFactory factory = fc.getApplication().getExpressionFactory();
            methodExpression = factory.createMethodExpression(fc.getELContext(), valueExpression,
                    expectedReturnType, expectedParamTypes);
        } catch (Exception e) {
            throw new FacesException("Method expression '" + valueExpression + "' could not be created.", e);
        }

        return methodExpression;
    }

    /**
     * Escanea el archivo columns.xhtml donde se definen las columnas
     * visualizadas en la grilla, y retorna las mismas.
     * 
     * @return Lista de columnas -> [header, field]
     */
    public List<Column> getColumns() {

        String id = "idListEntities";
        List<Column> columns = new LinkedList<Column>();
        UIExtendedDataTable table = (UIExtendedDataTable) findComponent(id);

        for (UIComponent ui : table.getChildren()) {
            if (ui instanceof UIColumn) {
                Column toAdd = buildColumn(ui);
                if (toAdd != null) {
                    columns.add(buildColumn(ui));
                }
            }
        }
        return columns;
    }

    /**
     * @param columns
     * @param ui
     */
    private Column buildColumn(UIComponent ui) {

        ValueExpression expressionHeader = ((HtmlOutputText) ((UIColumn) ui).getHeader())
                .getValueExpression(EL_VALUE_PROPERTY);

        String header = ELParser.getHeaderColumn(expressionHeader.getExpressionString());

        for (UIComponent children : ui.getChildren()) {
            if (children instanceof HtmlOutputText) {
                HtmlOutputText text = (HtmlOutputText) children;
                ValueExpression expression = text.getValueExpression(EL_VALUE_PROPERTY);

                String field = ELParser.getFieldByExpression(expression.getExpressionString());
                return new Column(header, field);
            }
        }
        return null;
    }

    /**
     * Clase que sirve de punto de acceso para convertir excepciones del tipo
     * que sea a excepciones manejadas por el sistema, si no puede convertirla
     * retorna la misma excepcion que recibio.
     * <p>
     * <b>Las excepciones manejadas son:</b>
     * <ol>
     * <li>{@link ConstraintViolationException}: delega su manejo a
     * {@link UniqueHelper#createUniqueException(Exception, Class)}</li>
     * </ol>
     * </p>
     * 
     * @param e
     *            excepcin que se desea manejar
     * @param clazz
     *            clase padre de la cual proviene la excepcion, esto se usa para
     *            obtener fields y anotaciones
     * @return Excepcin convertida, si se puede, y si no la misma
     */
    public Exception convertException(final Exception e, final Class<?> clazz) {

        if (e instanceof ConstraintViolationException) {
            return uniqueHelper.createUniqueException(e, clazz);
        }
        return e;
    }

    /**
     * Actualizamos los valores de todos los componentes hijos de un componente
     * cuyo id se pasa como parmetro, no toma en cuenta validaciones y deberia
     * funcionar igual a {@link UIComponentBase#processUpdates(FacesContext)}.
     * <p>
     * Se crea un nuevo mtodo que realiza el trabajo ya que el
     * {@link UIComponent#processUpdates(FacesContext)} no realiza correctamente
     * su trabajo si no se valida antes el formulario, esto se debe a quel el
     * flujo normal de JSF es primero validar y luego actualizar, que seran las
     * fases 3 y 4.
     * </p>
     * <p>
     * El proceso se describe a continuacin
     * <ol>
     * <li>Obtencin de elementos: se obtiene el componente a travs de su ID,
     * el contexto de faces y el contexto de las expresiones del lenguaje</li>
     * <li>Se obtiene un {@link KarakuConverter} para convertir los combos</li>
     * <li>Se itera sobre la lista de los hijos del componente a actualizar</li>
     * <ol>
     * <li>Se verifica si es un combobox, si este es el caso se convierte su
     * valor y se actualiza el objeto</li>
     * <li>Para otro caso, se obtiene su valor y se lo actualiza sin realizar
     * validaciones</li>
     * </ol>
     * </p>
     * 
     * </ol> <b>Observaciones:</b> no se verifica si algun valor no es
     * compatible con su destino, esto es si se ingresa una cadena en el lugar
     * de un numero, se lanzara una excepcion del tipo
     * 
     * @param componentID
     *            es el id del lado servidor del objeto que sera actualizado.
     */
    public void updateModel(String componentID) {

        // Obtenemos el id

        UIComponent formulario = findComponent(componentID);
        updateModel(formulario);
    }

    private void updateModel(UIComponent formulario) {

        FacesContext context = getContext();
        ELContext elContext = getContext().getELContext();
        Iterator<UIComponent> iter = formulario.getFacetsAndChildren();
        while (iter.hasNext()) {

            UIComponent component = iter.next();
            // Si es un valor submiteable, o INPUTEaBLE
            if (component instanceof UISelectOne) {
                UISelectOne com = (UISelectOne) component;
                Object newValue = com.getSubmittedValue();
                ValueExpression value = com.getValueExpression(EL_VALUE_PROPERTY);
                if (newValue != null && com.getConverter() != null) {
                    // Si tiene un converter definido, entonces utilizamos ese
                    // converter para obtener el valor
                    newValue = com.getConverter().getAsObject(context, com, newValue.toString());
                }
                value.setValue(elContext, newValue);
            } else if (component instanceof UICalendar) {
                UICalendar com = (UICalendar) component;
                Object newValue = com.getSubmittedValue();
                if (newValue != null) {
                    ValueExpression value = com.getValueExpression(EL_VALUE_PROPERTY);
                    newValue = getConverter().getAsObject(context, component, newValue.toString());
                    value.setValue(elContext, newValue);
                }

            } else if (component instanceof UIInput && !(component instanceof UICalendar)) {
                UIInput com = (UIInput) component;
                Object newValue = com.getSubmittedValue();
                ValueExpression value = com.getValueExpression(EL_VALUE_PROPERTY);
                if (value.getType(elContext).equals(Quantity.class)) {
                    if (StringUtils.isValid(newValue)) {
                        newValue = new Quantity((String) newValue);
                    } else {
                        newValue = Quantity.ZERO;
                    }
                }

                if (newValue instanceof String && !StringUtils.isValid(newValue)) {
                    newValue = null;
                }
                value.setValue(elContext, newValue);
            }
            updateModel(component);
        }
    }

    private DateTimeConverter getConverter() {

        DateTimeConverter converter = new DateTimeConverter();
        converter.setPattern(FormatProvider.DATE_FORMAT);
        converter.setTimeZone(TimeZone.getDefault());
        return converter;
    }

    @VisibleForTesting
    protected String getRealId(String id) {

        if (id.indexOf(':') != -1) {
            return getClientId(id);
        }
        return id;
    }

    /**
     * Finds component with the given id
     */
    @VisibleForTesting
    protected UIComponent findComponent(final UIComponent c, final String id) {

        if (id.equals(c.getId())) {
            return c;
        }
        Iterator<UIComponent> kids = c.getFacetsAndChildren();
        while (kids.hasNext()) {
            UIComponent found = findComponent(kids.next(), id);
            if (found != null) {
                return found;
            }
        }
        return null;
    }

    private String getString(final String code) {

        return i18nHelper.getString(code);
    }

    private String getString(final String code, Object... arguments) {

        return i18nHelper.getString(code, arguments);
    }

    /**
     * Retorna el contexto.
     * 
     * @return
     */
    @VisibleForTesting
    protected FacesContext getContext() {

        return FacesContext.getCurrentInstance();
    }
}