it.scoppelletti.programmerpower.console.spring.ConsoleApplicationRunner.java Source code

Java tutorial

Introduction

Here is the source code for it.scoppelletti.programmerpower.console.spring.ConsoleApplicationRunner.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.console.spring;

import java.util.*;
import org.slf4j.*;
import org.springframework.beans.factory.config.*;
import org.springframework.beans.factory.xml.*;
import org.springframework.context.support.*;
import org.springframework.core.env.*;
import it.scoppelletti.diagnostic.*;
import it.scoppelletti.programmerpower.*;
import it.scoppelletti.programmerpower.console.*;
import it.scoppelletti.programmerpower.console.spring.config.*;
import it.scoppelletti.programmerpower.spring.config.*;
import it.scoppelletti.programmerpower.threading.*;
import it.scoppelletti.programmerpower.types.*;
import it.scoppelletti.programmerpower.ui.*;

/**
 * Classe base per l&rsquo;esecuzione di un&rsquo;applicazione con
 * <ACRONYM TITLE="User Interface">UI</ACRONYM> su console.
 *  
 * <P>Le applicazioni Programmer Power su console normalmente derivano la classe
 * che implementa l&rsquo;applicazione dalla classe
 * {@code ConsoleApplicationRunner} e implementano il metodo statico
 * {@code main} di entry-point eseguendo il solo metodo {@link #run(String[])};
 * in questo modo le applicazioni dispongono automaticamente dei seguenti
 * servizi:</P>
 * 
 * <OL>
 * <LI>Acquisizione e validazione delle opzioni sulla linea di comando, incluse
 * le opzioni comuni a tutte le applicazioni, utilizzando le funzioni della
 * classe {@code CliStandardOptionSet}. 
 * <LI>Creazione del contesto dell&rsquo;applicazione Spring e suo rilascio al
 * termine dell&rsquo;applicazione stessa.
 * <LI>Esposizione dell&rsquo;eventuale messaggio di errore utilizzando la
 * <ACRONYM TITLE="User Interface">UI</ACRONYM> {@code ConsoleUI}.
 * <LI>Determinazione del codice di uscita di
 * <ACRONYM TITLE="Java Virtual Machine">JVM</ACRONYM>
 * ({@code JVMUtils.EXIT_SUCCESS} o {@code JVMUtils.EXIT_FAILURE}). 
 * </OL>
 *  
 * <P>In pratica il programmatore &egrave; libero di focalizzarsi sulle sole
 * funzioni specifiche dell&rsquo;applicazione: queste devono essere
 * implementate dal metodo {@code run} dell&rsquo;interfaccia {@code Runnable}
 * di un bean definito nel contesto Spring; il nome di questo bean deve
 * corrispondere al nome completo della classe dell&rsquo;oggetto
 * {@code ConsoleApplicationRunner} nel quale il carattere {@code "."} &egrave;
 * sostituito dal carattere {@code "-"}.</P>
 * 
 * <BLOCKQUOTE><PRE>
 * package org.company.product;
 * 
 * &#47;*
 *  * Program entry-point.
 *  *&#47;
 * public final class Program extends ConsoleApplicationRunner {
 *     private Program() {
 *     }
 *
 *     public static void main(String[] args) {
 *         Program appl = new Program();
 *       
 *         appl.run(args);         
 *     }
 *
 *     &#64;Override
 *     protected void initOptions(List&lt;CliOption&gt; list) {
 *         // Insert here your custom command line options...
 *         
 *         super.init(list);
 *     }
 *     
 *     &#64;Override
 *     protected void checkOptions() {
 *         // Insert here your custom checks over the command line options...
 *         
 *         super.checkOptions();
 *     }
 * }
 * 
 * &#47;*
 *  * Program implementation (bean named org-company-product-Program).
 *  *&#47;
 * public class ProgramBean implements Runnable {
 *      public ProgramBean() {
 *      }
 *      
 *      public void run() {
 *         // Insert here your application code...
 *         
 *         System.out.println("Hello world!");              
 *      }
 * }
 * </PRE></BLOCKQUOTE>
 * 
 * <H4 ID="idSpringCtx">1. Contesto Spring</H4>
 * 
 * <P>L&rsquo;oggetto {@code ConsoleApplicationRunner} inizializza il contesto
 * Spring rilevando le seguenti risorse attraverso il class-path:</P>
 * 
 * <OL>
 * <LI>La risorsa
 * {@code /it/scoppelletti/programmerpower/console/applicationContext.xml}.
 * <LI>La risorsa <ACRONYM TITLE="eXtensible Markup Language">XML</ACRONYM>
 * della quale il percorso corrisponde al nome completo della classe
 * dell&rsquo;oggetto {@code ConsoleApplicationRunner} (nell&rsquo;esempio, il
 * il nome della risorsa sarebbe {@code /org/company/product/Program.xml}); in
 * questo file di risorsa &egrave; normalmente definito il bean che implementa
 * l&rsquo;applicazione (nell&rsquo;esempio,
 * {@code org-company-product-Program}. 
 * <LI>Tutte le risorse {@code /META-INF/it-scoppelletti-moduleContext.xml}
 * rilevate attraverso il class-path. 
 * </OL>
 *  
 * <BLOCKQUOTE><PRE> 
 * &lt;beans ...&gt;
 *     &lt;bean id=&quot;org-company-product-Program&quot;
 *      class=&quot;org.company.product.ProgramBean&quot; /&gt;
 * &lt;/beans&gt;
 * </PRE></BLOCKQUOTE>
 * 
 * <P>L&rsquo;oggetto {@code ConsoleApplicationRunner} esegue le seguenti
 * personalizzazioni:</P>
 * 
 * <OL TYPE="1">
 * <LI>Registra se stesso come bean con il nome
 * {@code it-scoppelletti-programmerpower-console-consoleApplicationRunner}.
 * <LI>Registra il bean della <ACRONYM TITLE="User Interface">UI</ACRONYM>
 * {@code it-scoppelletti-programmerpower-ui-userInterface} implementato dalla
 * classe {@code ConsoleUI}.
 * <LI>Aggiunge il profilo {@code it-scoppelletti-console} nell&rsquo;ambiente
 * del contesto Spring (gli eventuali altri profili impostati attraverso la
 * propriet&agrave; di sistema {@code spring.profiles.active} sono comunque
 * considerati).
 * <LI>Inserisce come sorgenti delle propriet&agrave; di ambiente i seguenti
 * componenti:
 *      <OL TYPE="a">      
 *      <LI>Componente {@code ConsolePropertySource}.
 *      <LI>File di propriet&agrave;
 *      {@code it-scoppelletti-programmerpower-beans.properties} rilevato
 *      attraverso il class-path.       
 *      </OL> 
 * </OL>  
 * 
 * <H4>2. Termine di JVM</H4>
 * 
 * <P>Il metodo {@link #run} dell&rsquo;oggetto {@code ConsoleApplicationRunner}
 * registra un particolare thread di shutdown per essere eseguito al termine di
 * JVM (mediante il metodo
 * {@link it.scoppelletti.programmerpower.JVMUtils#addShutdownHook} della classe
 * statica {@code JVMUtils}) e rilasciare cos&igrave; tutte le risorse
 * dell&rsquo;applicazione non rilasciabili al termine del metodo {@code main}
 * dell&rsquo;applicazione.<BR>
 * In particolare, il thread di shutdown registrato rilascia le risorse relative
 * al framework Spring e al
 * {@linkplain it.scoppelletti.diagnostic.DiagnosticSystem#shutdown sistema di
 * diagnostica}.</P> 
 *  
 * @see   it.scoppelletti.programmerpower.spring.config.BeanConfigUtils#loadPropertySource
 * @see   it.scoppelletti.programmerpower.console.CliStandardOptionSet
 * @see   it.scoppelletti.programmerpower.console.ConsoleUI
 * @see   it.scoppelletti.programmerpower.console.spring.config.ConsolePropertySource  
 * @see   <A HREF="{@docRoot}/../reference/setup/envprops.html"
 *        TARGET="_top">Propriet&agrave; di ambiente</A> 
 * @since 2.0.0
 */
public abstract class ConsoleApplicationRunner extends CliStandardOptionSet {

    /**
     * Nome assegnato al thread principale dell&rsquo;applicazione. Il valore
     * della costante &egrave; <CODE>{@value}</CODE>.
     * 
     * @see it.scoppelletti.programmerpower.threading.ThreadContext
     */
    public static final String THREAD_NAME = "main";

    /**
     * Nome con il quale l&rsquo;oggetto {@code ConsoleApplicationRunner}
     * &egrave; registrato come bean. Il valore della costante &egrave;
     * <CODE>{@value}</CODE>.
     */
    public static final String BEAN_NAME = "it-scoppelletti-programmerpower-console-consoleApplicationRunner";

    /**
     * Nome della risorsa che contribuisce alla configurazione del contesto
     * Spring di un&rsquo;applicazione con UI su console. Il valore della
     * costante &egrave; <CODE>{@value}</CODE>.
     */
    public static final String CONSOLE_CONTEXT = "classpath:/it/scoppelletti/programmerpower/console/applicationContext.xml";

    /**
     * Profilo aggiunto nell&rsquo;ambiente del contesto Spring. Il valore
     * della costante &egrave; <CODE>{@value}</CODE>. 
     */
    public static final String PROFILE = "it-scoppelletti-console";

    private static final char CLASS_SEP = '.';
    private static final char RESOURCE_SEP = '/';
    private static final char BEAN_SEP = '-';
    private static final String CONTEXT_PREFIX = "classpath:/";
    private static final String CONTEXT_EXT = ".xml";

    private final String myApplId;
    private UUID myInstanceId;
    private GenericApplicationContext myApplCtx = null;

    /**
     * Costruttore.
     */
    protected ConsoleApplicationRunner() {
        myApplId = getClass().getCanonicalName();
        if (Strings.isNullOrEmpty(myApplId)) {
            throw new ReturnNullException(getClass().getName(), "getCanonicalName()");
        }
    }

    /**
     * Rilascia le risorse.
     */
    private void dispose() {
        if (myApplCtx != null) {
            myApplCtx.close();
            myApplCtx = null;
        }

        DiagnosticSystem.getInstance().shutdown();
    }

    /**
     * Restituisce la classe che rappresenta l&rsquo;applicazione.
     *
     * <P>Per classe che rappresenta un&rsquo;applicazione, si intende
     * normalmente la classe che implementa il metodo statico {@code main} di
     * entry-point dell&rsquo;applicazione stessa; questa informazione
     * pu&ograve; essere utilizzata per accedere ad alcune risorse specifiche
     * dell&rsquo;applicazione.</P>
     *
     * <P>Questa versione del metodo {@code getApplicationClass} restituisce la
     * classe che implementa l&rsquo;oggetto stesso.</P>
     * 
     * @return Classe.
     */
    @Override
    protected final Class<?> getApplicationClass() {
        return getClass();
    }

    /**
     * Restituisce l&rsquo;UUID dell&rsquo;istanza dell&rsquo;applicazione.
     * 
     * @return Valore.
     */
    public final UUID getInstanceId() {
        return myInstanceId;
    }

    /**
     * Esegue l&rsquo;applicazione.
     * 
     * @param args Argomenti sulla linea di comando.
     */
    protected final void run(String args[]) {
        int exitCode;

        try {
            exitCode = onRun(args);
        } catch (Throwable ex) {
            exitCode = JVMUtils.EXIT_FAILURE;
            ex.printStackTrace();
        }

        if (exitCode != 0) {
            System.exit(exitCode);
        }
    }

    /**
     * Esegue l&rsquo;applicazione.
     * 
     * @param  args Argomenti sulla linea di comando.
     * @return      Codice di uscita di JVM.
     */
    private int onRun(String[] args) {
        int exitCode;
        Runnable appl;
        UserInterface ui = new ConsoleUI();
        ThreadContext threadCtx = null;

        try {
            JVMUtils.addShutdownHook(new ConsoleApplicationRunner.ApplicationShutdown(this));

            MDC.put(DiagnosticSystem.MDC_APPLICATIONIDENTITY, myApplId);

            myInstanceId = UUIDGenerator.getInstance().newUUID();
            MDC.put(DiagnosticSystem.MDC_APPLICATIONINSTANCEID, myInstanceId.toString());

            threadCtx = new ThreadContext(ConsoleApplicationRunner.THREAD_NAME, myInstanceId);

            exitCode = acceptOptions(args);
            if (exitCode != CliStandardOptionSet.CONTINUE) {
                return exitCode;
            }

            myApplCtx = initApplicationContext(ui);
            appl = myApplCtx.getBean(initApplicationBeanName(), Runnable.class);
            appl.run();
        } catch (Exception ex) {
            ui.display(MessageType.ERROR, ex);
            return JVMUtils.EXIT_FAILURE;
        } finally {
            if (threadCtx != null) {
                threadCtx.dispose();
                threadCtx = null;
            }

            MDC.remove(DiagnosticSystem.MDC_APPLICATIONIDENTITY);
            MDC.remove(DiagnosticSystem.MDC_APPLICATIONINSTANCEID);
        }

        return JVMUtils.EXIT_SUCCESS;
    }

    /**
     * Inizializza il nome del bean che implementa l&rsquo;applicazione.
     * 
     * @return Valore.
     */
    private String initApplicationBeanName() {
        String name;

        name = myApplId.replace(ConsoleApplicationRunner.CLASS_SEP, ConsoleApplicationRunner.BEAN_SEP);
        return name;
    }

    /**
     * Inizializza il nome della risorsa del contesto dell&rsquo;applicazione.
     * 
     * @return Valore.
     */
    private String initApplicationContextResourceName() {
        StringBuilder buf;

        buf = new StringBuilder(ConsoleApplicationRunner.CONTEXT_PREFIX);
        buf.append(myApplId.replace(ConsoleApplicationRunner.CLASS_SEP, ConsoleApplicationRunner.RESOURCE_SEP));
        buf.append(ConsoleApplicationRunner.CONTEXT_EXT);

        return buf.toString();
    }

    /**
     * Inizializza il contesto dell&rsquo;applicazione.
     * 
     * @param  ui Interfaccia utente.
     * @return    Oggetto.
     */
    private GenericApplicationContext initApplicationContext(UserInterfaceProvider ui) {
        SingletonBeanRegistry beanRegistry;
        GenericApplicationContext ctx = null;
        GenericApplicationContext applCtx;
        XmlBeanDefinitionReader reader;
        ConfigurableEnvironment env;
        PropertySource<?> propSource;
        MutablePropertySources propSources;

        try {
            ctx = new GenericApplicationContext();
            reader = new XmlBeanDefinitionReader(ctx);
            reader.loadBeanDefinitions(ConsoleApplicationRunner.CONSOLE_CONTEXT);
            reader.loadBeanDefinitions(initApplicationContextResourceName());

            beanRegistry = ctx.getBeanFactory();
            beanRegistry.registerSingleton(ConsoleApplicationRunner.BEAN_NAME, this);
            beanRegistry.registerSingleton(UserInterfaceProvider.BEAN_NAME, ui);

            env = ctx.getEnvironment();

            // Acquisisco gli eventuali profili configurati prima di aggiungere
            // quello di Programmer Power
            env.getActiveProfiles();

            env.addActiveProfile(ConsoleApplicationRunner.PROFILE);

            propSources = env.getPropertySources();

            propSources.addFirst(new ConsolePropertySource(ConsolePropertySource.class.getName(), this));

            propSource = BeanConfigUtils.loadPropertySource();
            if (propSource != null) {
                propSources.addFirst(propSource);
            }

            ctx.refresh();
            applCtx = ctx;
            ctx = null;
        } finally {
            if (ctx != null) {
                ctx.close();
                ctx = null;
            }
        }

        return applCtx;
    }

    /**
     * Thread per il rilascio delle risorse dell&rsquo;applicazione.
     */
    private static final class ApplicationShutdown extends Thread {
        private final ConsoleApplicationRunner myAppl;

        /**
         * Costruttore.
         * 
         * @param appl Applicazione.
         */
        ApplicationShutdown(ConsoleApplicationRunner appl) {
            myAppl = appl;
        }

        /**
         * Esegue il thread.
         */
        @Override
        public void run() {
            myAppl.dispose();
        }
    }
}