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.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’esecuzione di un’applicazione con * <ACRONYM TITLE="User Interface">UI</ACRONYM> su console. * * <P>Le applicazioni Programmer Power su console normalmente derivano la classe * che implementa l’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’applicazione Spring e suo rilascio al * termine dell’applicazione stessa. * <LI>Esposizione dell’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 è libero di focalizzarsi sulle sole * funzioni specifiche dell’applicazione: queste devono essere * implementate dal metodo {@code run} dell’interfaccia {@code Runnable} * di un bean definito nel contesto Spring; il nome di questo bean deve * corrispondere al nome completo della classe dell’oggetto * {@code ConsoleApplicationRunner} nel quale il carattere {@code "."} è * sostituito dal carattere {@code "-"}.</P> * * <BLOCKQUOTE><PRE> * package org.company.product; * * /* * * Program entry-point. * */ * public final class Program extends ConsoleApplicationRunner { * private Program() { * } * * public static void main(String[] args) { * Program appl = new Program(); * * appl.run(args); * } * * @Override * protected void initOptions(List<CliOption> list) { * // Insert here your custom command line options... * * super.init(list); * } * * @Override * protected void checkOptions() { * // Insert here your custom checks over the command line options... * * super.checkOptions(); * } * } * * /* * * Program implementation (bean named org-company-product-Program). * */ * 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’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’oggetto {@code ConsoleApplicationRunner} (nell’esempio, il * il nome della risorsa sarebbe {@code /org/company/product/Program.xml}); in * questo file di risorsa è normalmente definito il bean che implementa * l’applicazione (nell’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> * <beans ...> * <bean id="org-company-product-Program" * class="org.company.product.ProgramBean" /> * </beans> * </PRE></BLOCKQUOTE> * * <P>L’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’ambiente * del contesto Spring (gli eventuali altri profili impostati attraverso la * proprietà di sistema {@code spring.profiles.active} sono comunque * considerati). * <LI>Inserisce come sorgenti delle proprietà di ambiente i seguenti * componenti: * <OL TYPE="a"> * <LI>Componente {@code ConsolePropertySource}. * <LI>File di proprietà * {@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’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ì tutte le risorse * dell’applicazione non rilasciabili al termine del metodo {@code main} * dell’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à di ambiente</A> * @since 2.0.0 */ public abstract class ConsoleApplicationRunner extends CliStandardOptionSet { /** * Nome assegnato al thread principale dell’applicazione. Il valore * della costante è <CODE>{@value}</CODE>. * * @see it.scoppelletti.programmerpower.threading.ThreadContext */ public static final String THREAD_NAME = "main"; /** * Nome con il quale l’oggetto {@code ConsoleApplicationRunner} * è registrato come bean. Il valore della costante è * <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’applicazione con UI su console. Il valore della * costante è <CODE>{@value}</CODE>. */ public static final String CONSOLE_CONTEXT = "classpath:/it/scoppelletti/programmerpower/console/applicationContext.xml"; /** * Profilo aggiunto nell’ambiente del contesto Spring. Il valore * della costante è <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’applicazione. * * <P>Per classe che rappresenta un’applicazione, si intende * normalmente la classe che implementa il metodo statico {@code main} di * entry-point dell’applicazione stessa; questa informazione * può essere utilizzata per accedere ad alcune risorse specifiche * dell’applicazione.</P> * * <P>Questa versione del metodo {@code getApplicationClass} restituisce la * classe che implementa l’oggetto stesso.</P> * * @return Classe. */ @Override protected final Class<?> getApplicationClass() { return getClass(); } /** * Restituisce l’UUID dell’istanza dell’applicazione. * * @return Valore. */ public final UUID getInstanceId() { return myInstanceId; } /** * Esegue l’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’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’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’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’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’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(); } } }