MokaByte 79 - 9mbre 2003 
Java Management Extentions
Gestione di applicazioni J2EE
II parte i DynamicMBeans
di
Giovanni Puliti
Questo mese vedremo brevemente come personalizzare il comportamento e le caratteristiche di un Mbean utilizzando l'interfaccia dinamica DynamicMBean. Come si potrà vedere la novità più importante è la possibilità e necessità di definire la modalità con la quale l'agent può rica-vare le informazioni relative a bean, non tramite la reflection (come accade per i bean standard) ma tramite il metodo getMBeanInfo().

Mbeans dinamici
L'utilizzo di mbeans dinamici permette una maggiore flessibilità e dinamicità nella definizione della interfaccia di management che in questo caso non è fissa e tipata ma generica e de-tipata.
I metodi offerti da questo genere di componenti permettono all'agent di scoprire dinamicamente attributi e funzionamento dell'oggetto gestito.
Questo genere di componenti è utile in quei casi in cui si prevede che l'interfaccia di management possa cambiare spesso, oppure quando è necessario non definire in fase di compi-lazione l'interfaccia: in questo caso specifico gli mbeans ad esempio permettono di utilizzare file XML con i quali specificare l'interfaccia a runtime.
Un altro caso in cui trovano applicazione questi componenti è quando si deve applicare JMX a risorse già esistenti o che non seguono lo standard delle convenzioni sui nomi di MBeans e Java Beans.
In questo caso gli MBeans dinamici si comportano come adapter interponendosi fra l'agent e la risorsa gestita invocandone i metodi appropriati.
Altra caratteristica importante è la definizione di metadati che sono a carico dell'mbean (e quindi del programmatore) e non dell'agent che esegue questo compito tramite l'introspezione.

 

Interfaccia
Per poter creare un mbean dinamico è necessario estendere l'interfaccia DynamicMBean, che espone una serie di metodi per la determinazione degli attributi e per invocare i metodi. I me-todi relativi agli attributi, così già come quelli dell'invocazione negli MBeans Standard, sono dinamici e detipati: per questo come verrà mostrato è necessaria una certa disciplina nella loro implementazione.

public interface DynamicMBean{
Object getAttribute(String attribute)
AttributeList getAttributes(String[] attributes)
MBeanInfo getMBeanInfo()
Object invoke(String actionName, Object[] params, String[] signature)
void setAttribute(Attribute attribute)
AttributeList setAttributes(AttributeList attributes)

Si noti come il metodo invoke() in questa caso accetti oggetti stringhe non solo nei parametri ma anche un array di stringhe per la firma del metodo da invocare.
I due array di oggetti devono essere ovviamente della stessa dimensione ed i valori contenuti devono corrispondere.
Il metodo getMBeanInfo() restituisce un oggetto di tipo MbeanInfo, che permette di descrivere completamente il bean tramite metadati su attributi, metodi, costruttori e notifiche di eventi. Ognuno di questi metadati corrisponde una classe apposita che verrà presentata poco più avan-ti. Nel proseguo dell'articolo viene mostrato un esempio di implementazione del metodo relati-vo alla versione dinamica di UserMBean visto in precedenza.
Il metodo getAttribute() ha la seguente firma

Object getAttribute(String attribute) throws AttributeNotFoundException, MBeanException, ReflectionException

Si noti il rilancio della AttributeNotFoundException che deve essere propagata in caso l'attributo richiesto non esista.
Ad esempio il metodo getName() visto nell'mbean Hello potrebbe in questo caso diventare

public String getAttribute (String attribute) throws AttributeNotFoundException, AttributeInvalidException
MBeanException,ReflectionException{

if (attribute.equals("name")
return name;

// mettere qui altri controlli e restituire l'attributo corrispondente
. . .
// se alla fine nessun attributo viene restituito significa che l'attributo non esiste
// o il nome non è esatto
throw new AttributeNotFoundException("Attributo "+attribute+" non trovato");
}

In questo caso si effettua una semplice comparazione con il parametro passato e gli attributi del bean: i nomi reali degli attributi e quelli esposti dall'interfaccia sono del tutto slegati. La logica di comparazione all'interno del metodo definisce di fatto il set di attributi validi. Il tipo ritorna-to deve anche corrispondere a quanto definito all'interno del Metadata Object corrispondente all'attributo.

L'eccezione MBeanException serve per incapsulare eccezioni di altro tipo che genericamente vengono create al momento di ricavare una proprietà, come la IOException (se i dati devono essere letti) o la NullPointerException (se i dati non sono validi).
ReflectionException infine può verificarsi in occasione di operazioni di reflection a carico dell'agent. Si parlerà in seguito di questi aspetti.
Cercare di ricavare un attributo null o stringa vuota crea una IllegalArgumentException che viene wrappata dall'agent con una JMRuntimeException.
Il metodo getAttributes() opera restituendo una AttributeList, contenuta nella JMX API e che discende direttamente ArrayList. La lista contiene elementi di tipo Attribute. Il metodo non se-gnala eccezioni di nessun tipo in caso di errore nel ricavare gli attributi: tutti gli attributi che generano un qualche problema nell'operazione di lettura vengono lasciati fuori dalla lista.

I metodi setAttribute() e setAttributeList() servono per impostare gli attributi del bean. Anche in questo caso, come per i getXXX è compito del programmatore cercare la corrispondenza giusta fra il nome dell'attributo e l'attributo vero e proprio. La firma completa del primo meto-do è

void setAttribute(Attribute attribute) throws AttributeNotFoundException, InvalidAttributeException
MBeanException,ReflectionException

Dove le eccezioni rilanciate anche in questo caso consentono di avere informazioni in caso di errori sui nomi (AttributeNotFoundException) o di tipi non compatibili (InvalidAttributeException). Le eccezioni checked devono invece essere wrappate e rilanciate come MBeanException.

public String setAttribute (Attribute attribute) throws AttributeNotFoundException,
AttributeInvalidException,
MBeanException, ReflectionException{

if (attribute == null)
throw new AttributeNotFoundException("Attributo nullo");

if (attribute.getName().equals("Name"))
this.name = (String)attribute.getValue();
}


Il metodo setAttributes() si comporta in modo simile al getAttributes(). Riceve in ingresso un AttributeList e non genera errori o messaggi in caso di errori nella associazione fra nome attributo ed attributo stesso.

 

Invocazione
Con i bean dinamici è necessario anche ridefinire il metodo invoke() in modo da consentire l'esatta invocazione del metodo corrispondente. Ad esempio la versione dinamica del MBean potrebbe avere il metodo invoke() così definito

public Object invoke(String actionName, Object[] params, String[] signature)throws MBeanException, ReflectionException{
if (actionName == null || actionName.equals(""))
throw new IllegalArgumentException("Nessuna operazione definita");
// mappa il nome della operazione sui metodi da eseguire

if(actionName.equals(PRINT_INFO)){
return this.printInfo();
}
if(actionName.equals(ADD_NUMBER)){
this.addPhoneNumber((String)params[0]);
return null;
}
if(actionName.equals(REMOVE_NUMBER)){
this.removePhoneNumber((String)params[0]);
return null;
}
else throw new UnsupportedOperationException("Operazione "+actionName+" sconosciuta");
}

 

Il metodo getMBeanInfo()
Il metodo getMBeanInfo() permette all'agent di ricavare tutte le informazioni relative al bean. Come si può vedere dal codice seguente esso confeziona tutta una serie di metadati e poi li restituisce in un MBeanInfo.

public MBeanInfo getMBeanInfo(){
// per determinare se l'attributo leggibile e/o scrivibile
final boolean READABLE = true;
final boolean WRITABLE = true;

// per determinare se attributo è ricavabile nella forma booleana isAttribute()
final boolean IS_GETTER = true;

// descrizione della classe dell'MBean
String className = this.getClass().getName();
String description = "Una risorsa per utenti con interfaccia di gestione dinamica";

// metadata per l'attributo Id
MBeanAttributeInfo id = new MBeanAttributeInfo(ID, // nome attributo
String.class.getName(), // tipo attributo
"Identificatore unico utente", // descrizione attributo
READABLE, !WRITABLE, !IS_GETTER // accesso attributo
);

// metadata per l'attributo Name
MBeanAttributeInfo name = new MBeanAttributeInfo(NAME, // nome attributo
String.class.getName(), // tipo attributo
"Nome utente", // descrizione attributo
READABLE, // accesso attributo
WRITABLE,
!IS_GETTER
);
// metadata per l'attributo Address
MBeanAttributeInfo address = new MBeanAttributeInfo(ADDRESS, // nome attributo
String.class.getName(), // tipo attributo
"Indirizzo utente", // descrizione attributo
READABLE, WRITABLE, !IS_GETTER // accesso attributo
);


// metadata per l'attributo Password
MBeanAttributeInfo password = new MBeanAttributeInfo(PASSWORD, // nome attributo
String.class.getName(), // tipo attributo
"Identificatore unico dello user", // descrizione attributo
READABLE, !WRITABLE, !IS_GETTER // accesso attributo
);


// metadata per l'attributo PhoneNumbers
MBeanAttributeInfo numbers = new MBeanAttributeInfo(PHONENUMBERS, // nome attributo
Vector.class.getName(), // tipo attributo
"Identificatore unico dello user", // descrizione attributo
READABLE, !WRITABLE, !IS_GETTER // accesso attributo
);

// metadata per l'operazione printInfo()
MBeanOperationInfo printInfo = new MBeanOperationInfo(PRINT_INFO, // nome operazione
"Restituisce le informazioni sull'utente", // descrizione testuale
null, // signature
String.class.getName(), // tipo ritornato
MBeanOperationInfo.INFO); // impatto

// metadata per l'operazione addPhoneNumber()
MBeanOperationInfo addPhoneNumber = new MBeanOperationInfo(ADD_NUMBER, // nome operazione
"Aggiunge un numero di telefono", // descrizione testuale
new MBeanParameterInfo[] { // signature
new MBeanParameterInfo("number",String.class.getName(),
"Il numero da aggiungere")
},
void.class.getName(), // tipo ritornato
MBeanOperationInfo.ACTION // impatto
);


// metadata per l'operazione removePhoneNumber()
MBeanOperationInfo removePhoneNumber = new MBeanOperationInfo(REMOVE_NUMBER, // nome operazione
"Rimuove un numero di telefono", // descrizione testuale
new MBeanParameterInfo[] { // signature
new MBeanParameterInfo("number",
String.class.getName(),
"Il numero da aggiungere")
},
void.class.getName(), // tipo ritornato
MBeanOperationInfo.ACTION // impatto
);


//mbean costruttore
MBeanConstructorInfo defaultConstructor = new MBeanConstructorInfo("Default Constructor", "Crea una nuova istanza di utente", null);

// attribute Constructor and operation list
MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[]{id, name, address, numbers, password};
MBeanConstructorInfo[] constructors = new MBeanConstructorInfo[]{defaultConstructor};
MBeanOperationInfo[] operations = new MBeanOperationInfo[]{printInfo, addPhoneNumber, removePhoneNumber};

return new MBeanInfo(className, description, attributes, constructors, operations, null);

}

Il codice completo dell'esempio

Di seguito è riportato il codice completo della classe DynamicUser che può essere scaricata nella sezione risorse e link

public MBeanInfo getMBeanInfo(){

  // Attributi boolenani per determinare
  // se l'attributo leggibile e/o scrivibile
  final boolean READABLE = true;
  final boolean WRITABLE = true;

  // Attributi boolenano per determinare se un attributo
  // è ricavabile nella forma booleana isAttribute()
  final boolean IS_GETTER = true;

  // descrizione della classe dell'MBean
  String className = this.getClass().getName();
  String description = "Una risorsa per utenti con interfaccia di gestione dinamica";

  // metadato per l'attributo Id
  MBeanAttributeInfo id = new MBeanAttributeInfo(ID, // nome attributo
                              String.class.getName(), // tipo attributo
                              "Identificatore unico utente", // descrizione attributo
                              READABLE, !WRITABLE, !IS_GETTER // accesso
  );

  // metadato per l'attributo Name
  MBeanAttributeInfo name;
 
name = new MBeanAttributeInfo(NAME, // nome attributo
                                String.class.getName(), // tipo attributo
                                "Nome utente", // descrizione attributo
                                READABLE, WRITABLE, !IS_GETTER // accesso attributo
                                );
  // metadato per l'attributo Address
  MBeanAttributeInfo address;
  address = new MBeanAttributeInfo(ADDRESS, // nome attributo
                                   String.class.getName(), // tipo attributo
                                   "Indirizzo utente", // descrizione attributo
                                   READABLE, WRITABLE, !IS_GETTER // accesso
  );


  // metadato per l'attributo Password
  MBeanAttributeInfo password;
  password = new MBeanAttributeInfo(PASSWORD, // nome attributo
                                    String.class.getName(), // tipo attributo
                                    "Identificatore unico dello user", // descrizione
                                    READABLE, !WRITABLE, !IS_GETTER // accesso
  );


  // metadato per l'attributo PhoneNumbers
  MBeanAttributeInfo numbers;
  numbers = new MBeanAttributeInfo(PHONENUMBERS, // nome attributo
                                   Vector.class.getName(), // tipo attributo
                                   "Identificatore unico dello user", // descrizione
                                   READABLE, !WRITABLE, !IS_GETTER // accesso
                                   );

  // metadato per l'operazione printInfo()
  MBeanOperationInfo printInfo;
  printInfo = new MBeanOperationInfo(PRINT_INFO, // nome
                                     "Fornisce informazioni utente", // descrizione
                                                                     // testuale
                                     null, // signature
                                     String.class.getName(), // tipo ritornato
                                     MBeanOperationInfo.INFO); // impatto

  // metadato per l'operazione addPhoneNumber()
  MBeanOperationInfo addPhoneNumber;
  addPhoneNumber = new MBeanOperationInfo(ADD_NUMBER, // nome operazione
                                          "Aggiunge un numero di telefono", // descr.
                      new MBeanParameterInfo[] { // signature
                          new MBeanParameterInfo("number",String.class.getName(),
                                                 "Il numero da aggiungere")
                          },
                      void.class.getName(), // tipo ritornato
                      MBeanOperationInfo.ACTION // impatto
                      );


  // metadato per l'operazione removePhoneNumber()
  MBeanOperationInfo removePhoneNumber;
  removePhoneNumber = new MBeanOperationInfo(REMOVE_NUMBER, // nome operazione
                                             "Rimuove un numero di telefono",//descr
                                             new MBeanParameterInfo[] { // signature
                                             new MBeanParameterInfo("number",
                                             String.class.getName(),
                                             "Il numero da aggiungere")
                                             },
                                             void.class.getName(), // tipo ritornato
                                             MBeanOperationInfo.ACTION // impatto
                                             );


  //mbean costruttore
  MBeanConstructorInfo defaultConstructor;
  defaultConstructor = new MBeanConstructorInfo("Default   Constructor",
                                                "Crea una nuova istanza di utente",                                                 null);

  // lista attributo costruttore e metodi
  MBeanAttributeInfo[] attributes = new MBeanAttributeInfo[]{id, name,
                                                             address,
                                                             numbers,                                                              password};
  MBeanConstructorInfo[] constructors;
  constructors = new MBeanConstructorInfo[]{defaultConstructor};
  MBeanOperationInfo[] operations;
  operations = new MBeanOperationInfo[]{printInfo,
                                        addPhoneNumber,
                                        removePhoneNumber};

  return new MBeanInfo(className, description, attributes,
                       constructors, operations, null);

 }

  public String printInfo(){
    return "ID: "+ Id +"\n name: "+Name+"\naddress: "+ Address+"\npassword:            "+Password+"\nPhoneNumbers: "+PhoneNumbers;
  }


  // metodi di management JMX
  public void addPhoneNumber(String number){
    PhoneNumbers.addElement(number);
  }

  public void removePhoneNumber(String number){
    PhoneNumbers.remove(number);
  }

  public DynamicUser() {}

}

Conclusioni
Questo mese si è visto un altro importante pezzo del framwork JMX. L'esempio allegato dovrebbe poter dare maggiore spiegazione del funzionamento e del significato di quanto visto.
Nelle prossime puntate gli argomenti che verranno trattati daranno maggiore giustificazione di quanto qui mostrato

 

Link e risorse
[JMXSpec] - JMX Sun official reference: http://java.sun.com/products/JavaManagement
[JMXRI] - JMX Reference Implementation
http://java.sun.com/products/JavaManagement/download
[TIV] IBM JMX Tivoli implementation : http://www.alphaworks.ibm.com
[ANET] AdvanceNet JMX implementation: http://www.advancenet.com
[JMXJB] "JMX: managing J2EE with Java Management Extensions" di J. Lindfors, M.Fleury, The JBoss Group; Ed. SAMS.

Scarica i sorgenti completi

 
MokaByte® è un marchio registrato da MokaByte s.r.l. 
Java®, Jini® e tutti i nomi derivati sono marchi registrati da Sun Microsystems.
Tutti i diritti riservati. E' vietata la riproduzione anche parziale.
Per comunicazioni inviare una mail a info@mokabyte.it