/* * Copyright 2013 GiavaCms.org. * * Licensed under the Eclipse Public License version 1.0, available at * http://www.eclipse.org/legal/epl-v10.html */ package org.giavacms.common.controller; import java.io.Serializable; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.util.List; import javax.annotation.PostConstruct; import javax.faces.application.FacesMessage; import javax.faces.application.FacesMessage.Severity; import javax.faces.context.FacesContext; import javax.faces.model.DataModel; import org.giavacms.common.annotation.BackPage; import org.giavacms.common.annotation.EditPage; import org.giavacms.common.annotation.ListPage; import org.giavacms.common.annotation.OwnRepository; import org.giavacms.common.annotation.PrintPage; import org.giavacms.common.annotation.ViewPage; import org.giavacms.common.model.LocalDataModel; import org.giavacms.common.model.Search; import org.giavacms.common.repository.Repository; import org.giavacms.common.util.BeanUtils; import org.giavacms.common.util.JSFUtils; import org.giavacms.common.util.StringUtils; import org.jboss.logging.Logger; /** * * @param <T> */ public abstract class AbstractController<T> implements Serializable { private static final long serialVersionUID = 1L; // ------------------------------------------------ // --- Logger ------------------------------------- // ------------------------------------------------ protected final Logger logger = Logger.getLogger(getClass().getCanonicalName()); /** * Entity class */ private Class<T> entityClass; /** * Search object */ protected Search<T> search; // ------------------------------------------------ // --- Stato dell'handler ------------------------- // ------------------------------------------------ private static final int PAGE_SIZE = 10; private static final int ROWS_PER_PAGE = 10; private static final int SCROLLER_PAGE = 1; private int rowCount; private int pageSize = PAGE_SIZE; private int rowsPerPage = ROWS_PER_PAGE; private int scrollerPage = SCROLLER_PAGE; public static final String REDIRECT_PARAM = "?faces-redirect=true"; /** * Risultato della ricerca */ private DataModel<T> model; /** * Risultato della selezione, del caricamento diretto o della crezione di un nuovo entity */ private T element; /** * Risultato multiplo della selezione */ private List<T> elements; /** * Riga attualmente selezionata per le modifiche inline. */ private T rowElement; /** * Stato della pagina di gestione */ private Boolean editMode; /** * Toggle dei campi in lettura/scrittura per gestire casi di una stessa pagina in cui un utente può solo leggere * alcuni campi, mentre un altro li può modificare e salvare (ad esempio quando per entity molto semplici non ci sono * una pagina di gestione e una di visualizzazione, ma una sola pagina e alcuni utenti non devono poter editare i * dati) */ private boolean readOnlyMode; /** * Pagina di provenienza, settabile dall'esterno attraverso i corrispondenti metodi Possibile override per forzare * una determinata backpage tramite gli handler concreti che estendono questa classe */ private String backPage = null; /** * Pagina per la vista elenco */ private String listPage = null; /** * Pagina per la vista dettaglio */ private String viewPage = null; /** * Pagina per la vista modifica */ private String editPage = null; /** * Pagina per la stampa */ private String printPage = null; /** * Repository per fare query su db */ private Repository<T> repository; /** * Variabile booleana di default a true che, se settata a false nel default criteria della classe che implementa, * permette di non caricare la lista prima di avere opportuni parametri di ricerca (ad esempio in caso di query * pesanti) */ private boolean loaded = true; // ------------------------------------------------ // --- Costruttore interno ------------------------ // ------------------------------------------------ /** * Costruttore con parametri da invocare obbligatoriamente nel costruttore senza argomenti dei sotto-handler: * inizializza i parametri di ricerca e colleziona eventuali vincoli session-wide come quelli che discendono dalla * identita' dell'utente loggato */ // @SuppressWarnings({ "unchecked", "rawtypes" }) // public AbstractController(Class<T> clazz) { // this.entityClass = clazz; // search = new Search(clazz); // // defaultCriteria(); // } /** * */ @SuppressWarnings({ "unchecked", "rawtypes" }) public AbstractController() { this.entityClass = getClassType(); // defaultCriteria(); search = new Search(this.entityClass); } @PostConstruct private void init() { injectRepositoryAndPages(); initController(); defaultCriteria(); } /** * Metodo per inizializzare i controller */ public void initController() { } @SuppressWarnings({ "rawtypes", "unchecked" }) private void injectRepositoryAndPages() { // Field[] fields = getClass().getDeclaredFields(); for (Field field : fields) { try { PrintPage print_anno = field.getAnnotation(PrintPage.class); BackPage back_anno = field.getAnnotation(BackPage.class); ListPage list_anno = field.getAnnotation(ListPage.class); EditPage edit_anno = field.getAnnotation(EditPage.class); ViewPage view_anno = field.getAnnotation(ViewPage.class); OwnRepository repository_anno = field .getAnnotation(OwnRepository.class); if (print_anno != null) { field.setAccessible(true); Object page = field.get(null); this.printPage = "" + page; } if (back_anno != null) { field.setAccessible(true); Object page = field.get(null); this.backPage = "" + page; } if (list_anno != null) { field.setAccessible(true); Object page = field.get(null); this.listPage = "" + page; } if (edit_anno != null) { field.setAccessible(true); Object page = field.get(null); this.editPage = "" + page; } if (view_anno != null) { field.setAccessible(true); Object page = field.get(null); this.viewPage = "" + page; } try { if (repository_anno != null) { Class clazz = repository_anno.value(); this.repository = (Repository<T>) BeanUtils.getBean(clazz); } } catch (Exception e) { e.printStackTrace(); } } catch (IllegalArgumentException e) { logger.info(e.getMessage()); } catch (IllegalAccessException e) { logger.info(e.getMessage()); } } } /** * @return */ @SuppressWarnings({ "rawtypes", "unchecked" }) private Class<T> getClassType() { Class clazz = getClass(); while (!(clazz.getGenericSuperclass() instanceof ParameterizedType)) { clazz = clazz.getSuperclass(); } ParameterizedType parameterizedType = (ParameterizedType) clazz .getGenericSuperclass(); // ParameterizedType parameterizedType = (ParameterizedType) getClass() // .getSuperclass().getGenericSuperclass(); return (Class<T>) parameterizedType.getActualTypeArguments()[0]; } // ------------------------------------------------ // --- getter/setter------------------------------- // ------------------------------------------------ /** * @return */ public Search<T> getSearch() { return this.search; } /** * @return */ public DataModel<T> getModel() { if (model == null) refreshModel(); return model; } /** * @param model */ public void setModel(DataModel<T> model) { this.model = model; } /** * @return */ public T getElement() { return element; } /** * @param element */ public void setElement(T element) { this.element = element; } /** * @return */ public List<T> getElements() { return elements; } /** * @param elements */ public void setElements(List<T> elements) { this.elements = elements; } public T getRowElement() { return rowElement; } public void setRowElement(T rowElement) { this.rowElement = rowElement; } /** * @return */ public boolean isEditMode() { return editMode != null ? editMode : this.element == null ? false : getId(this.element) == null ? false : true; } /** * @param editMode */ public void setEditMode(boolean editMode) { this.editMode = editMode; } /** * @return */ public int getRowCount() { return rowCount; } /** * @param rowCount */ public void setRowCount(int rowCount) { this.rowCount = rowCount; } /** * @return */ public int getPageSize() { return pageSize; } /** * @param pageSize */ public void setPageSize(int pageSize) { this.pageSize = pageSize; } /** * @return */ public int getRowsPerPage() { return rowsPerPage; } /** * @param rowsPerPage */ public void setRowsPerPage(int rowsPerPage) { this.rowsPerPage = rowsPerPage; } /** * @return */ public int getScrollerPage() { return scrollerPage; } /** * @param scrollerPage */ public void setScrollerPage(int scrollerPage) { this.scrollerPage = scrollerPage; } public boolean isReadOnlyMode() { return readOnlyMode; } public void setReadOnlyMode(boolean roMode) { this.readOnlyMode = roMode; } /** * Metodo per ottenere l'id di ricerca (il nome del campo non è noto a priori e uguale per tutti gli entity... almeno * finché non introduciamo interfacce a questo scopo... ;) ) * * Serve per poter fornire una implementazione a default del metodo refreshModel(), senza doverlo riscrivere in ogni * sotto-handler, come invece fatto in passato */ public Object getId(T t) { try { Field f = t.getClass().getDeclaredField("id"); f.setAccessible(true); return f.get(t); } catch (Exception e) { e.printStackTrace(); return null; } } /** * Metodo per ottenere l'ejb generico che effettua la ricerca * * Serve per poter fornire una implementazione a default del metodo refreshModel(), senza doverlo riscrivere in ogni * sotto-handler, come invece fatto in passato * * @return */ public Repository<T> getRepository() { return repository; } // ============================================================================================ // === LOGICA DI GESTIONE DELLO STATO INTERNO DELL'HANDLER // ============================================================================================ /** * Metodo interno (protected) che carica il risultato della ricerca corrente. Possibile override da parte delle * sottoclassi per aggiungere listener e/o effettuare operazioni aggiuntive specifiche del particolare sotto-handler */ public void refreshModel() { // setModel(new LocalLazyDataModel<T>(search, getRepository())); setModel(new LocalDataModel<T>(pageSize, search, getRepository())); } /** * Metodo interno (protected) da overriddare per assicurare che i criteri di ricerca contengano sempre tutti i * vincoli desiderati (es: identità utente, selezioni esterne, oggetti figli non nulli, ecc...) * * Qui ne viene fornita una implementazione di default che non fa nulla, per evitare di scrivere un metodo vuoto nei * sotto-handler in caso non ce ne sia bisogno */ public void defaultCriteria() { } /** * Implements reset specific operations */ protected void preReset() { this.scrollerPage = 1; this.element = null; this.model = null; search = new Search<T>(this.entityClass); defaultCriteria(); } /** * Metodo per forzare dall'esterno la pulizia dell'handler * * Possibili override da parte dei sotto-handler per resettare ulteriori campi non previsti dall'handler generico o * per modificare la vista di destinazione ottenuta come outcome */ public String reset() { preReset(); return listPageNoRedirect(); } /** * Implements reload specific operations */ protected void preReload() { this.element = null; this.model = null; this.loaded = true; } /** * Metodo per forzare l'aggiornamento del risultato della ricerca dell'handler, SENZA PERDERE I CRITERI DI RICERCA * CORRENTI!!! * * Possibili override da parte dei sotto-handler per resettare ulteriori campi non previsti dall'handler generico o * per modificare la vista di destinazione ottenuta come outcome */ public String reload() { preReload(); getOrderByParameter(); return listPageNoRedirect(); } /** * Metodo per forzare dall'esterno la pulizia dell'handler in modo trasparente all'utente (es: value di un * outputText), in modo da ripulire eventuali inconsistenze dovute all'uso del pulsante back del browser in luogo del * pulsante 'indietro' o 'annulla' delle pagine */ public boolean getClear() { reset(); return true; } public String reloadAjax() { reload(); return null; } public String resetAjax() { reset(); return null; } // ============================================================================================ // === LOGICA DI NAVIGAZIONE // ================================================================== // ============================================================================================ /* * Questi metodi non iniziano per get perché sono da usare nelle action dei componenti ui (method binding * expression), non come valori (value binding expression... get/set.. dot notation... you know...) */ public void getOrderByParameter() { String orderBy = (String) JSFUtils.getParameter("orderBy"); if (!StringUtils.isEmpty(orderBy)) { search.setOrder(orderBy); } } /** * Pagina di provenienza, settabile dall'esterno (cioè da altri handler!) Possibile override per forzare una * determinata backpage tramite gli handler concreti che estendono questa classe */ public void backPage(String backPage) { this.backPage = backPage; } /** * @return */ public String backPage() { return backPage; } /** * @return */ public String viewPage() { return viewPage + REDIRECT_PARAM; } /** * @return */ public String listPage() { return listPage + REDIRECT_PARAM; } /** * @return */ public String listPageNoRedirect() { return listPage; } /** * @return */ public String editPage() { return editPage + REDIRECT_PARAM; } /** * @return */ public String editPageNoRedirect() { return editPage; } /** * @return */ public String printPage() { return printPage; } // ============================================================================================ // === LOGICA DI BUSINESS // ============================================================================================ public String addElement() { try { this.element = getRepository().create(entityClass); } catch (Exception e) { logger.info(e.getMessage()); e.printStackTrace(); } this.editMode = false; this.readOnlyMode = false; // impostazioni locali // da specializzare in sottoclassi // vista di destinazione return editPage(); } public String viewElement() { // fetch dei dati T t = (T) getModel().getRowData(); t = getRepository().fetch(getId(t)); // impostazioni locali // da specializzare in sottoclassi // settaggi nel super handler this.element = t; this.editMode = false; this.readOnlyMode = true; // vista di destinazione return viewPage(); } /** * @return */ public String modElement() { // fetch dei dati; T t = (T) getModel().getRowData(); t = getRepository().fetch(getId(t)); // impostazioni locali // da specializzare in sottoclassi // settaggi nel super handler this.element = t; this.editMode = true; this.readOnlyMode = false; // vista di destinazione return editPage(); } /** * @return */ public String printElement() { // fetch dei dati; T t = (T) getModel().getRowData(); t = getRepository().fetch(getId(t)); // impostazioni locali // da specializzare in sottoclassi // settaggi nel super handler this.element = t; // vista di destinazione return printPage(); } public String view(Object key) { T t = getRepository().find(key); // impostazioni locali // da specializzare in sottoclassi // settaggi nel super handler this.element = t; this.editMode = false; this.readOnlyMode = true; // vista di destinazione return viewPage(); } // --------------------------------------------------------------------------------------------------------------------- // --- metodi che richiedono un oggetto element gia' valido e ne modificano // solo lo stato in sessione ------------------ // --------------------------------------------------------------------------------------------------------------------- /** * @return */ public String modCurrent() { // fetch dei dati element = getRepository().fetch(getId(element)); editMode = true; readOnlyMode = false; // vista di arrivo return editPage(); } /** * @return */ public String viewCurrent() { // fetch dei dati element = getRepository().fetch(getId(element)); editMode = false; readOnlyMode = true; // vista di arrivo return viewPage(); } /** * @return */ public String printCurrent() { // fetch dei dati element = getRepository().fetch(getId(element)); // vista di arrivo return printPage(); } // ---------------------------------------------------------------------------------------------------------- // --- metodi che richiedono un oggetto element gia' valido e ne modificano // lo stato su db ------------------ // ---------------------------------------------------------------------------------------------------------- /** * @return */ public String save() { try { // recupero e preelaborazioni dati in input // nelle sottoclassi!! ovverride! // salvataggio setElement(getRepository().persist(getElement())); // refresh locale setEditMode(false); setReadOnlyMode(true); refreshModel(); return viewPage(); } catch (Exception exc) { return editPageNoRedirect(); } } /** * @return */ public String update() { try { // recupero dati in input // nelle sottoclassi!! ovverride! // salvataggio getRepository().update(getElement()); // refresh locale setElement(getRepository().fetch(getId(getElement()))); setEditMode(false); setReadOnlyMode(true); refreshModel(); return viewPage(); } catch (Exception exc) { return editPageNoRedirect(); } } /** * @return */ public String delete() { try { // operazione su db getRepository().delete(getId(element)); // refresh super handler setEditMode(false); setReadOnlyMode(true); refreshModel(); element = null; return listPage(); } catch (Exception exc) { return editPageNoRedirect(); } } public String modInline() { T rowElement = (T) getModel().getRowData(); setRowElement(rowElement); setModificabile(rowElement, true); return null; } @SuppressWarnings("unchecked") public void updateInline() { T rowElement = (T) getModel().getRowData(); setRowElement(null); // setModificabile(rowElement,false); if (getRepository().update(rowElement)) { List<T> rows = (List<T>) getModel().getWrappedData(); rows.set(getModel().getRowIndex(), getRepository().find(getId(rowElement))); } else { addFacesMessage("Errore nell'aggiornamento dei dati"); } } @SuppressWarnings("unchecked") public void annullaInline() { T rowElement = (T) getModel().getRowData(); setRowElement(null); List<T> rows = (List<T>) getModel().getWrappedData(); rows.set(getModel().getRowIndex(), getRepository().find(getId(rowElement))); setModificabile(rowElement, false); } public void deleteInline() { T rowElement = (T) getModel().getRowData(); setRowElement(null); if (getRepository().delete(getId(rowElement))) { setModel(null); } else { addFacesMessage("Errore durante la cancellazione"); } } protected void setModificabile(T t, boolean modificabile) { try { Field modificabileField = t.getClass().getDeclaredField( "modificabile"); modificabileField.setAccessible(true); modificabileField.set(t, modificabile); } catch (Exception e) { logger.error(e.getMessage(), e); } } // -------------------------------------------------------------------------- // Varie // -------------------------------------------------------------------------- public boolean isLoaded() { return loaded; } public void setLoaded(boolean loaded) { this.loaded = loaded; } // -------------------------------------------------------------------------- // Varie // -------------------------------------------------------------------------- protected void addFacesMessage(String summary, String message) { addFacesMessage(null, summary, message, ""); } protected void addFacesMessage(String summary) { addFacesMessage(null, summary, summary, ""); } protected void addFacesMessage(Severity severity, String summary, String message, String forComponentId) { FacesMessage fm = new FacesMessage(message); fm.setSummary(summary); if (severity != null) { fm.setSeverity(severity); } else { fm.setSeverity(FacesMessage.SEVERITY_ERROR); } FacesContext.getCurrentInstance().addMessage(forComponentId, fm); } }