it.scoppelletti.programmerpower.web.control.ActionBase.java Source code

Java tutorial

Introduction

Here is the source code for it.scoppelletti.programmerpower.web.control.ActionBase.java

Source

/*
 * Copyright (C) 2011 Dario Scoppelletti, <http://www.scoppelletti.it/>.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package it.scoppelletti.programmerpower.web.control;

import java.io.*;
import java.util.*;
import javax.annotation.*;
import com.opensymphony.xwork2.*;
import com.opensymphony.xwork2.interceptor.*;
import org.slf4j.*;
import org.springframework.beans.factory.annotation.*;
import org.springframework.context.ApplicationContext;
import org.springframework.core.env.*;
import org.springframework.web.util.*;
import it.scoppelletti.programmerpower.*;
import it.scoppelletti.programmerpower.reflect.*;
import it.scoppelletti.programmerpower.types.*;
import it.scoppelletti.programmerpower.ui.*;
import it.scoppelletti.programmerpower.web.view.*;

/**
 * Classe base delle azioni.
 * 
 * @since 1.0.0
 */
public abstract class ActionBase extends ActionSupport implements Disposable, Preparable,
        InterceptorExclusionSupport, PreResultListener, HelpUrlProvider, WebUI.Provider {
    private static final long serialVersionUID = 1L;

    /**
     * Stato della vista {@code viewId}: Id&#46; della vista.
     */
    public static final String VIEWSTATE_VIEWID = "viewId";

    /**
     * Risultato {@code cancel}.
     */
    public static final String CANCEL = "cancel";

    /**
     * Nome della propriet&agrave; tra le risorse associate all&rsquo;azione
     * sulla quale deve essere impostato il titolo della vista. Il valore della
     * costante &egrave; <CODE>{@value}</CODE>.
     * 
     * @see #getViewTitle
     */
    public static final String PROP_VIEWTITLE = "label.title";

    /**
     * Nome della propriet&agrave; tra le risorse associate all&rsquo;azione
     * sulla quale pu&ograve; essere impostato il percorso della pagina HTML di
     * guida dell&rsquo;azione. Il valore della costante &egrave; 
     * <CODE>{@value}</CODE>.
     * 
     * @see #getHelpUrl
     */
    public static final String PROP_HELPPATH = "path.help";

    /**
     * Nome della propriet&agrave; di ambiente sulla quale pu&ograve; essere
     * impostato l&rsquo;indirizzo di base della guida di riferimento di 
     * Programmer Power. Il valore della costante &egrave; 
     * <CODE>{@value}</CODE>.
     * 
     * @see #getHelpUrl
     * @see <A HREF="{@docRoot}/../reference/setup/envprops.html"
     *      TARGET="_top">Propriet&agrave; di ambiente</A>  
     */
    public static final String PROP_HELPURL = "it.scoppelletti.programmerpower.web.help.url";

    /**
     * Valore di default dell&rsquo;URL di base della guida di riferimento di 
     * Programmer Power. Il valore della costante &egrave; 
     * <CODE>{@value}</CODE>.
     * 
     * @see #getHelpUrl
     */
    public static final String DEF_HELPURL = "http://www.scoppelletti.it/programmerpower/reference";

    private static final String ACTION_SUFFIX = "Action";
    private static final Logger myLogger = LoggerFactory.getLogger(ActionBase.class);

    /**
     * @serial Stato della vista.
     */
    private ViewState myViewState;

    /**
     * @serial Indicatore di risorse rilasciate.
     */
    private boolean myIsDisposed = false;

    private transient String myActionNameBase;
    private transient String myReplacingViewId;
    private transient String myClosingDialogId;
    private transient List<ActionMessage> myMessages;

    @Resource(name = UserInterfaceProvider.BEAN_NAME)
    private transient UserInterfaceProvider myUI;

    @Autowired
    private transient ApplicationContext myApplCtx;

    /**
     * Costruttore.
     */
    protected ActionBase() {
        myMessages = new ArrayList<ActionMessage>();
    }

    public void dispose() {
        myIsDisposed = true;
    }

    /**
     * Serializza l&rsquo;oggetto.
     * 
     * @param      out Flusso di scrittura.
     * @serialData     Formato di default seguito dai messaggi:
     * 
     * <P><OL>
     * <LI>Numero dei messaggi.
     * <LI>Sequenza dei messaggi.
     * </OL></P> 
     */
    private void writeObject(ObjectOutputStream out) throws IOException {
        out.defaultWriteObject();

        out.writeInt(myMessages.size());
        for (ActionMessage msg : myMessages) {
            out.writeObject(msg);
        }
    }

    /**
     * Deserializza l&rsquo;oggetto.
     * 
     * @param in Flusso di lettura.
     */
    private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
        int i, n;

        in.defaultReadObject();

        myMessages = new ArrayList<ActionMessage>();
        n = in.readInt();
        for (i = 0; i < n; n++) {
            myMessages.add((ActionMessage) in.readObject());
        }
    }

    /**
     * Restituisce lo stato della vista.
     * 
     * @return Oggetto.
     * @see    #setViewState
     */
    @Final
    public ViewState getViewState() {
        if (myViewState == null) {
            myViewState = new ViewState();
        }

        return myViewState;
    }

    /**
     * Imposta lo stato della vista.
     * 
     * @param obj Oggetto.
     * @see       #getViewState
     */
    @Final
    public void setViewState(ViewState obj) {
        if (obj == null) {
            throw new ArgumentNullException("obj");
        }

        myViewState = obj;
    }

    /**
     * Restituisce la base del nome delle operazioni dell&rsquo;azione.
     * 
     * @return Valore.
     */
    @Final
    public String getActionNameBase() {
        if (myActionNameBase == null) {
            myActionNameBase = initActionNameBase();
        }

        return myActionNameBase;
    }

    /**
     * Inizializza la base del nome delle operazioni dell&rsquo;azione;.
     * 
     * @return Valore.
     */
    protected String initActionNameBase() {
        char c;
        String value;

        value = getClass().getSimpleName();
        if (Strings.isNullOrEmpty(value)) {
            return "";
        }

        if (value.endsWith(ActionBase.ACTION_SUFFIX)) {
            value = value.substring(0, value.length() - ActionBase.ACTION_SUFFIX.length());
            if (value.isEmpty()) {
                return "";
            }
        }

        c = value.charAt(0);
        if (Character.isUpperCase(c)) {
            value = String.valueOf(Character.toLowerCase(c)).concat(value.substring(1));
        }

        return value;
    }

    /**
     * Restituisce il titolo della vista.
     * 
     * <P>La versione di default del metodo {@code getViewTitle} restituisce
     * il valore della propriet&agrave; {@code label.title} tra le risorse
     * associate all&rsquo;azione.</P>
     * 
     * @return Valore.
     */
    public String getViewTitle() {
        return getText(ActionBase.PROP_VIEWTITLE);
    }

    /**
     * Restituisce l&rsquo;indirizzo della pagina HTML di guida.
     * 
     * <P>Questa implementazione del metodo {@code getHelpUrl} rileva il valore 
     * della propriet&agrave; {@code path.help} tra le risorse
     * associate all&rsquo;azione: se questa propriet&agrave; &egrave;
     * impostata, &egrave; considerata come un percorso relativo
     * all&rsquo;indirizzo di base della guida di riferimento di Programmer
     * Power ({@code http://www.scoppelletti.it/programmerpower/reference});
     * questo indirizzo di base pu&ograve; essere sovrascritto dal valore
     * impostato sulla propriet&agrave; di ambiente
     * {@code it.scoppelletti.programmerpower.web.help.url}.<BR>
     * Le classi derivate da terze parti dovrebbero implementare una versione
     * prevalente del metodo {@code getHelpUrl} per restituire l&rsquo;URL
     * opportuno.</P>
     * 
     * @return Valore. Se la guida non &egrave; prevista, restituisce
     *         {@code null}.
     * @see <A HREF="{@docRoot}/../reference/setup/envprops.html"
     *      TARGET="_top">Propriet&agrave; di ambiente</A>          
     */
    public String getHelpUrl() {
        String base, path;
        UriComponentsBuilder uriBuilder;
        Environment env;

        path = getText(ActionBase.PROP_HELPPATH);
        if (Strings.isNullOrEmpty(path) || ActionBase.PROP_HELPPATH.equals(path)) {
            return null;
        }

        if (myApplCtx == null) {
            base = ActionBase.DEF_HELPURL;
        } else {
            env = myApplCtx.getEnvironment();
            base = env.getProperty(ActionBase.PROP_HELPURL, ActionBase.DEF_HELPURL);
        }

        if (!base.endsWith("/") && !path.startsWith("/")) {
            base = base.concat("/");
        }

        uriBuilder = UriComponentsBuilder.fromUriString(base);
        uriBuilder.path(path);

        return uriBuilder.build().toUriString();
    }

    /**
     * Restituisce l&rsquo;id&#46; della vista da rimpiazzare con la nuova
     * vista.
     * 
     * @return Valore. Se la vista sulla quale proiettare il risultato non deve
     *         rimpiazzare nessun&rsquo;altra vista, restituisce {@code null}.
     * @see    #VIEWSTATE_VIEWID 
     */
    @Final
    public String getReplacingViewId() {
        return myReplacingViewId;
    }

    /**
     * Restituisce l&rsquo;id&#46; della vista corrispondente al dialogo da
     * chiudere.
     * 
     * @return Valore. Se non deve essere chiuso alcun dialogo, restituisce
     *         {@code null}. 
     * @see    #setClosingDialogId
     */
    @Final
    public String getClosingDialogId() {
        return myClosingDialogId;
    }

    /**
     * Imposta l&rsquo;id&#46; della vista corrispondente al dialogo da
     * chiudere.
     * 
     * @param value Valore.
     * @see         #getClosingDialogId
     */
    @Final
    public void setClosingDialogId(String value) {
        myClosingDialogId = value;
    }

    /**
     * Restituisce una copia non modificabile della collezione dei messaggi.
     * 
     * @return Collezione.
     */
    @Final
    public List<ActionMessage> getMessages() {
        synchronized (myMessages) {
            return Collections.unmodifiableList(myMessages);
        }
    }

    /**
     * Restituisce l&interfaccia utente.
     * 
     * @return Oggetto.
     */
    @Final
    protected UserInterfaceProvider getUserInterface() {
        if (myUI == null) {
            throw new PropertyNotSetException(toString(), "userInterface");
        }

        return myUI;
    }

    @Final
    public boolean isDisposed() {
        return myIsDisposed;
    }

    /**
     * Verifica se un intercettore deve essere escluso per l&rsquo;esecuzione di
     * un metodo.
     * 
     * <P>Questa implementazione del metodo
     * {@code isInterceptorExcludedForMethod} non esclude alcun
     * intercettore.</P>
     * 
     * @param  interceptor Nome dell&rsquo;intercettore.
     * @param  method      Nome del metodo.
     * @return             Esito della verifica.
     */
    public boolean isInterceptorExcludedForMethod(String interceptor, String method) {
        return false;
    }

    /**
     * Preparazione generica.
     * 
     * <P>Il metodo {@code prepare} &egrave; eseguito dopo il metodo di
     * preparazione specifico del metodo in esecuzione.<BR>
     * Questa implementazione del metodo {@code prepare}, rileva l&rsquo;id&#46;
     * della vista sulla quale &egrave; stato proiettato l&rsquo;ultimo
     * risultato.</P> 
     * 
     * @see #VIEWSTATE_VIEWID
     * @see #getReplacingViewId 
     */
    public void prepare() throws Exception {
        String viewId;

        viewId = getViewState().get(ActionBase.VIEWSTATE_VIEWID);
        myReplacingViewId = Strings.isNullOrEmpty(viewId) ? null : viewId;
    }

    /**
     * Esegue le operazioni preventive alla produzione del risultato.
     * 
     * <P>Questa implementazione del metodo {@code beforeResult} esegue le
     * seguenti operazioni:</P>
     * 
     * <OL>
     * <LI>Se l&rsquo;id&#46; della vista sulla quale deve essere proiettato il
     * risultato non &egrave; ancora stato impostato, lo imposta attraverso il
     * metodo {@code initViewId}.
     * <LI>Determina se la vista sulla quale deve essere proiettato il risultato
     * rimpiazza la vista sulla quale era stato proiettato il risultato
     * precedente.
     * </OL>
     * 
     * @param invocation Stato di esecuzione dell&rsquo;azione.
     * @param resultCode Codice del risultato.
     * @see              #VIEWSTATE_VIEWID 
     * @see              #getReplacingViewId
     * @see              #initViewId
     */
    public void beforeResult(ActionInvocation invocation, String resultCode) {
        String viewId;

        viewId = getViewState().get(ActionBase.VIEWSTATE_VIEWID);
        if (Strings.isNullOrEmpty(viewId)) {
            viewId = initViewId();
            if (Strings.isNullOrEmpty(viewId)) {
                throw new ReturnNullException(toString(), "initViewId");
            }

            getViewState().put(ActionBase.VIEWSTATE_VIEWID, viewId);
            myReplacingViewId = null;
            myLogger.trace("View id. \"{}\" assigned.", viewId);
        } else if (viewId.equals(myReplacingViewId)) {
            myReplacingViewId = null;
        }

        if (myReplacingViewId != null) {
            myLogger.debug("Replacing view id. \"{}\" by view id. \"{}\".", myReplacingViewId, viewId);
        }
    }

    /**
     * Inizializza l&rsquo;id&#46; della vista.
     * 
     * <P>La versione di default del metodo {@code initViewId} combina il nome
     * completo della classe dell&rsquo;azione con un UUID casuale per garantire
     * l&rsquo;univocit&agrave; del valore restituito.</P>
     * 
     * @return Valore. Non pu&ograve; restituire {@code null} n&eacute; la
     *         stringa vuota {@code ""}.
     * @see    #beforeResult
     */
    protected String initViewId() {
        String prefix;
        StringBuilder buf, suffix;
        UUID uuid;

        prefix = getClass().getCanonicalName();
        if (Strings.isNullOrEmpty(prefix)) {
            // Classe anonima:
            // Utilizzo il nome della classe di base.
            prefix = ActionBase.class.getCanonicalName();
        }

        buf = new StringBuilder(prefix);
        Strings.replace(buf, '.', '-');
        buf.append('-');

        uuid = UUIDGenerator.getInstance().newUUID();
        suffix = new StringBuilder(uuid.toString());
        Strings.removeAll(suffix, '-');
        buf.append(suffix);

        return buf.toString();
    }

    /**
     * Aggiunge un messaggio di azione.
     * 
     * @param      msg Messaggio.
     * @deprecated     Utilizzare l&rsquo;{@linkplain #getUserInterface
     *                 interfaccia utente}.             
     */
    @Override
    public void addActionMessage(String msg) {
        MessageEvent event;

        if (myUI != null) {
            myUI.display(MessageType.INFORMATION, msg);
        } else {
            event = new MessageEvent(MessageType.INFORMATION, msg, null);
            display(event);
        }
    }

    /**
     * Rimuove tutti i messaggi di azione.
     */
    @Override
    public void clearMessages() {
        int i;

        super.clearMessages();

        synchronized (myMessages) {
            for (i = myMessages.size() - 1; i >= 0; i--) {
                switch (myMessages.get(i).getEvent().getMessageType()) {
                case ERROR:
                    break;

                default:
                    myMessages.remove(i);
                }
            }
        }
    }

    /**
     * Aggiunge un messaggio di errore.
     * 
     * @param      msg Messaggio.
     * @deprecated     Utilizzare l&rsquo;{@linkplain #getUserInterface
     *                 interfaccia utente}.            
     */
    @Override
    public void addActionError(String msg) {
        MessageEvent event;

        if (myUI != null) {
            myUI.display(MessageType.ERROR, msg);
        } else {
            event = new MessageEvent(MessageType.ERROR, msg, null);
            display(event);
        }
    }

    /**
     * Rimuove tutti i messaggi di errore.
     */
    @Override
    public void clearActionErrors() {
        int i;

        super.clearActionErrors();

        synchronized (myMessages) {
            for (i = myMessages.size() - 1; i >= 0; i--) {
                switch (myMessages.get(i).getEvent().getMessageType()) {
                case ERROR:
                    myMessages.remove(i);
                    break;
                }
            }
        }
    }

    /**
     * Rimuove tutti i messaggi sia di errore che di azione.
     */
    @Override
    public void clearErrorsAndMessages() {
        super.clearErrorsAndMessages();

        synchronized (myMessages) {
            myMessages.clear();
        }
    }

    public void display(MessageEvent event) {
        synchronized (myMessages) {
            myMessages.add(new ActionMessage(event));

            switch (event.getMessageType()) {
            case ERROR:
                super.addActionError(event.getMessage());
                break;

            case INFORMATION:
                super.addActionMessage(event.getMessage());
                break;

            case WARNING:
                super.addActionMessage(event.getMessage());
                break;

            default:
                throw new EnumConstantNotPresentException(MessageType.class, event.getMessageType().name());
            }
        }
    }
}