Java tutorial
/* * 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. della vista. */ public static final String VIEWSTATE_VIEWID = "viewId"; /** * Risultato {@code cancel}. */ public static final String CANCEL = "cancel"; /** * Nome della proprietà tra le risorse associate all’azione * sulla quale deve essere impostato il titolo della vista. Il valore della * costante è <CODE>{@value}</CODE>. * * @see #getViewTitle */ public static final String PROP_VIEWTITLE = "label.title"; /** * Nome della proprietà tra le risorse associate all’azione * sulla quale può essere impostato il percorso della pagina HTML di * guida dell’azione. Il valore della costante è * <CODE>{@value}</CODE>. * * @see #getHelpUrl */ public static final String PROP_HELPPATH = "path.help"; /** * Nome della proprietà di ambiente sulla quale può essere * impostato l’indirizzo di base della guida di riferimento di * Programmer Power. Il valore della costante è * <CODE>{@value}</CODE>. * * @see #getHelpUrl * @see <A HREF="{@docRoot}/../reference/setup/envprops.html" * TARGET="_top">Proprietà di ambiente</A> */ public static final String PROP_HELPURL = "it.scoppelletti.programmerpower.web.help.url"; /** * Valore di default dell’URL di base della guida di riferimento di * Programmer Power. Il valore della costante è * <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’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’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’azione. * * @return Valore. */ @Final public String getActionNameBase() { if (myActionNameBase == null) { myActionNameBase = initActionNameBase(); } return myActionNameBase; } /** * Inizializza la base del nome delle operazioni dell’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à {@code label.title} tra le risorse * associate all’azione.</P> * * @return Valore. */ public String getViewTitle() { return getText(ActionBase.PROP_VIEWTITLE); } /** * Restituisce l’indirizzo della pagina HTML di guida. * * <P>Questa implementazione del metodo {@code getHelpUrl} rileva il valore * della proprietà {@code path.help} tra le risorse * associate all’azione: se questa proprietà è * impostata, è considerata come un percorso relativo * all’indirizzo di base della guida di riferimento di Programmer * Power ({@code http://www.scoppelletti.it/programmerpower/reference}); * questo indirizzo di base può essere sovrascritto dal valore * impostato sulla proprietà 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’URL * opportuno.</P> * * @return Valore. Se la guida non è prevista, restituisce * {@code null}. * @see <A HREF="{@docRoot}/../reference/setup/envprops.html" * TARGET="_top">Proprietà 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’id. della vista da rimpiazzare con la nuova * vista. * * @return Valore. Se la vista sulla quale proiettare il risultato non deve * rimpiazzare nessun’altra vista, restituisce {@code null}. * @see #VIEWSTATE_VIEWID */ @Final public String getReplacingViewId() { return myReplacingViewId; } /** * Restituisce l’id. 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’id. 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’esecuzione di * un metodo. * * <P>Questa implementazione del metodo * {@code isInterceptorExcludedForMethod} non esclude alcun * intercettore.</P> * * @param interceptor Nome dell’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} è eseguito dopo il metodo di * preparazione specifico del metodo in esecuzione.<BR> * Questa implementazione del metodo {@code prepare}, rileva l’id. * della vista sulla quale è stato proiettato l’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’id. della vista sulla quale deve essere proiettato il * risultato non è 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’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’id. della vista. * * <P>La versione di default del metodo {@code initViewId} combina il nome * completo della classe dell’azione con un UUID casuale per garantire * l’univocità del valore restituito.</P> * * @return Valore. Non può restituire {@code null} né 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’{@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’{@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()); } } } }