SeamFramework.orgCommunity Documentation

Capitolo 5. Configurare i componenti Seam

5.1. Configurare i componenti tramire impostazioni di proprietà
5.2. Configurazione dei componenti tramite components.xml
5.3. File di configurazione a grana fine
5.4. Tipi di proprietà configurabili
5.5. Uso dei namespace XML

La filosofia di minimizzare la configurazione basata su XML è estremamente forte in Seam. Tuttavia ci sono varie ragioni per voler configurare i componente Seam tramite XML: per isolare le informazioni specifiche del deploy dal codice Java, per abilitare la creazione di framework riutilizzabili, per configurare le funzionalità predefinite di Seam, ecc. Seam fornisce due approcci base per configurare i componenti: configurazione tramire impostazioni di proprietà in un file di proprietà o in web.xml, e configurazione tramite components.xml.

I componenti Seam possono essere accompagnati da proprietà di configurazioni o via parametri di contesto servlet oppure tramite un file di proprietà chiamato seam.properties collocato nella radice del classpath.

Il componente Seam configurabile deve esporre metodi setter di stile JavaBeans per gli attributi configurabili. Se un componente Seam chiamato com.jboss.myapp.settings ha un metodo setter chiamato setLocale(), si può scrivere una proprietà chiamata com.jboss.myapp.settings.locale nel file seam.properties o come parametro di contesto servlet, e Seam imposterà il valore dell'attributo locale quando istanzia il componente.

Lo stesso meccanismo viene usato per configurare lo stesso Seam. Per esempio, per impostare il timeout della conversazione, si fornisce un valore a org.jboss.seam.core.manager.conversationTimeout in web.xml oppure in seam.properties. (C'è un componente Seam predefinito chiamato org.jboss.seam.core.manager con metodo setter chiamato setConversationTimeout().)

Il file components.xml è un poco più potente delle impostazioni di proprietà. Esso consente di:

Un file components.xml può apparire in una delle tre seguenti posizioni:

Solitamente i componenti Seam vengono installati quando lo scanner di deploy scopre una classe con una annotazione @Name collocata in un archivio con un file seam.properties o un file META-INF/components.xml. (Amenoché il componente abbia una annotazione @Install che indichi che non debba essere installato di default). Il file components.xml consente di gestire i casi speciali in cui occorra fare override delle annotazioni.

Per esempio, il seguente file components.xml installa jBPM:


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xmlns:bpm="http://jboss.com/products/seam/bpm">
    <bpm:jbpm/>
</components
>

Questo esempio fa la stessa cosa:


<components>
    <component class="org.jboss.seam.bpm.Jbpm"/>
</components
>

Questo installa e configura due differenti contesti di persistenza gestiti da Seam:


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:persistence="http://jboss.com/products/seam/persistence"

    <persistence:managed-persistence-context name="customerDatabase"
                       persistence-unit-jndi-name="java:/customerEntityManagerFactory"/>
        
    <persistence:managed-persistence-context name="accountingDatabase"
                       persistence-unit-jndi-name="java:/accountingEntityManagerFactory"/>            

</components
>

Ed anche questo fa lo stesso:


<components>
    <component name="customerDatabase" 
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/customerEntityManagerFactory</property>
    </component>
    
    <component name="accountingDatabase"
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/accountingEntityManagerFactory</property>
    </component>
</components
>

Questo esempio crea un contesto di persistenza gestito da Seam con scope di sessione (questa non è una pratica raccomandata):


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:persistence="http://jboss.com/products/seam/persistence"

  <persistence:managed-persistence-context name="productDatabase" 
                                          scope="session"
                     persistence-unit-jndi-name="java:/productEntityManagerFactory"/>        

</components
>

<components>
            
    <component name="productDatabase"
              scope="session"
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/productEntityManagerFactory</property>
    </component>

</components
>

E' comune utilizzare l'opzione auto-create per gli oggetti infrastrutturali quali i contesti di persistenza, che risparmia dal dovere specificare esplicitamente create=true quando si usa l'annotazione @In.


<components xmlns="http://jboss.com/products/seam/components" 
            xmlns:persistence="http://jboss.com/products/seam/persistence"

  <persistence:managed-persistence-context name="productDatabase" 
                                    auto-create="true"
                     persistence-unit-jndi-name="java:/productEntityManagerFactory"/>        

</components
>

<components>
            
    <component name="productDatabase"
        auto-create="true"
              class="org.jboss.seam.persistence.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName"
>java:/productEntityManagerFactory</property>
    </component>

</components
>

La dichiarazione <factory> consente di specificare un valore o un'espressione di method binding che verrà valutata per inizializzare il valore di una variabile di contesto quando viene referenziata la prima volta.


<components>

    <factory name="contact" method="#{contactManager.loadContact}" scope="CONVERSATION"/>

</components
>

Si può creare un "alias" (un secondo nome) per un componente Seam in questo modo:


<components>

    <factory name="user" value="#{actor}" scope="STATELESS"/>

</components
>

Si può anche creare un "alias" per un'espressione comunemente usata:


<components>

    <factory name="contact" value="#{contactManager.contact}" scope="STATELESS"/>

</components
>

E' comune vedere usato auto-create="true" con la dichiarazione <factory>:


<components>

    <factory name="session" value="#{entityManager.delegate}" scope="STATELESS" auto-create="true"/>

</components
>

A volte si vuole riutilizzare lo stesso file components.xml con piccoli cambiamenti durante il deploy ed il testing. Seam consente di mettere dei wildcard della forma @wildcard@ nel file components.xml che può essere rimpiazzato o dallo script Ant (a deployment time) o fornendo un file chiamato components.properties nel classpath (a development time). Si vedrà usato quest'ultimo approccio negli esempi di Seam.

Qualora si abbia un grande numero di componenti che devono essere configurati in XML, è più sensato suddividere l'informazione di components.xml in numerosi piccoli file. Seam consente di mettere la configurazione per una classe non anonima, per esempio, com.helloworld.Hello in una risorsa chiamata com/helloworld/Hello.component.xml. (Potresti essere familiare a questo pattern, poiché è lo stesso usato da Hibernate.) L'elemento radice del file può essere o un elemento <components> oppure <component>.

La prima opzione lascia definire nel file componenti multipli:


<components>
    <component class="com.helloworld.Hello" name="hello">
        <property name="name"
>#{user.name}</property>
    </component>
    <factory name="message" value="#{hello.message}"/>
</components
>

La seconda opzione lascia definire o configurare un solo componente, ma è meno rumorosa:


<component name="hello">
    <property name="name"
>#{user.name}</property>
</component
>

Nella seconda opzione, il nome della classe è implicato nel file in cui appare la definizione del componente.

In alternativa, si può mettere una configurazione per tutte le classi nel pacchetto com.helloworld in com/helloworld/components.xml.

Le proprietà dei tipi stringa, primitivi o wrapper primitivi possono essere configurati solo come atteso:

org.jboss.seam.core.manager.conversationTimeout 60000

<core:manager conversation-timeout="60000"/>

<component name="org.jboss.seam.core.manager">
    <property name="conversationTimeout"
>60000</property>
</component
>

Anche array, set e liste di stringhe o primitivi sono supportati:

org.jboss.seam.bpm.jbpm.processDefinitions order.jpdl.xml, return.jpdl.xml, inventory.jpdl.xml

<bpm:jbpm>
    <bpm:process-definitions>
        <value
>order.jpdl.xml</value>
        <value
>return.jpdl.xml</value>
        <value
>inventory.jpdl.xml</value>
    </bpm:process-definitions>
</bpm:jbpm
>

<component name="org.jboss.seam.bpm.jbpm">
    <property name="processDefinitions">
        <value
>order.jpdl.xml</value>
        <value
>return.jpdl.xml</value>
        <value
>inventory.jpdl.xml</value>
    </property>
</component
>

Anche le mappe con chiavi associate a stringhe oppure valori stringa o primitivi sono supportati:


<component name="issueEditor">
    <property name="issueStatuses">
        <key
>open</key
> <value
>open issue</value>
        <key
>resolved</key
> <value
>issue resolved by developer</value>
        <key
>closed</key
> <value
>resolution accepted by user</value>
    </property>
</component
>

Quando si configurano le proprietà multivalore, Seam preserverà di default l'ordine in cui vengono messi gli attributi in components.xml (amenoché venga usato SortedSet/SortedMap allora Seam userà TreeMap/TreeSet). Se la proprietà ha un tipo concreto (per esempio LinkedList) Seam userà quel tipo.

Si può anche fare l'override del tipo specificando un nome di classe pienamente qualificato:


<component name="issueEditor">
   <property name="issueStatusOptions" type="java.util.LinkedHashMap">
      <key
>open</key
> <value
>open issue</value>
      <key
>resolved</key
> <value
>issue resolved by developer</value>
      <key
>closed</key
> <value
>resolution accepted by user</value>
   </property>
</component
>

Infine si può unire assieme i componenti usando un'espressione value-binding. Si noti che è diverso dall'usare l'iniezione con @In, poiché avviene al momento dell'istanziamento del componente invece che al momento dell'invocazione. E' quindi molto più simile alle strutture con dependency injection offerte dai tradizionali IoC container come JSF o Spring.


<drools:managed-working-memory name="policyPricingWorkingMemory"
    rule-base="#{policyPricingRules}"/>

<component name="policyPricingWorkingMemory"
    class="org.jboss.seam.drools.ManagedWorkingMemory">
    <property name="ruleBase"
>#{policyPricingRules}</property>
</component
>

Seam risolve anche un'espressione stringa EL prima di assegnare il valore iniziale alla proprietà del bean del componente. Quindi si possono iniettare alcuni dati di contesto nei componenti.


<component name="greeter" class="com.example.action.Greeter">
    <property name="message"
>Nice to see you, #{identity.username}!</property>
</component
>

C'è un'importante eccezione. Se un tipo di proprietà a cui il valore iniziale assegnato è o una ValueExpression di Seam o una MethodExpression, allora la valutazione di EL è rimandata. Invece il wrapper dell'espressione appropriata viene creato e assegnato alla proprietà. I modelli di messaggi nel componente Home dell'Applicazione Framework di Seam servono da esempio.


<framework:entity-home name="myEntityHome"
    class="com.example.action.MyEntityHome" entity-class="com.example.model.MyEntity"
    created-message="'#{myEntityHome.instance.name}' has been successfully added."/>

Dentro il componente si può accedere all'espressione di stringa chiamando getExpressionString() sulla ValueExpression o MethodExpression. Se la proprietà è una ValueExpression, si può risolvere il valore usando getValue() e se la proprietà è un MethodExpression, si può invocare il metodo usando invoke(Object args...). Ovviamente per assegnare un valore alla proprietà MethodExpression, l'intero valore iniziale deve essere una singola espressione EL.

Attraverso gli esempi ci sono stati due modi per dichiarare i componenti: con e senza l'uso di namespace XML. Il seguente mostra un tipico file components.xml senza namespace:


<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xsi:schemaLocation="http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd">

    <component class="org.jboss.seam.core.init">
        <property name="debug"
>true</property>
        <property name="jndiPattern"
>@jndiPattern@</property>
    </component>
    
</components
>

Come si può vedere, è abbastanza prolisso. Ancor peggio, i nomi del componente e dell'attributo non possono essere validati a development time.

La versione con namespace appare come:


<?xml version="1.0" encoding="UTF-8"?>
<components xmlns="http://jboss.com/products/seam/components"
            xmlns:core="http://jboss.com/products/seam/core"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation=
                "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.1.xsd 
                 http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd">

    <core:init debug="true" jndi-pattern="@jndiPattern@"/>

</components
>

Anche se le dichiarazioni di schema sono lunghe, il contenuto vero di XML è piatto e facile da capire. Gli schemi forniscono informazioni dettagliate su ogni componente e sugli attributi disponibili, consentendo agli editor XML di offrire un autocompletamento intelligente. L'uso di elementi con namespace semplifica molto la generazione ed il mantenimento in uno stato corretto dei file components.xml.

Questo funziona bene per i componenti predefiniti di Seam, ma per i componenti creati dall'utente? Ci sono due opzioni. La prima, Seam supporta un misto dei due modelli, consentendo l'uso delle dichiarazioni generiche <component> for i componenti utente, assieme alle dichiarazioni con namespace dei componenti predefiniti. Ma ancor meglio, Seam consente di dichiarare in modo veloce i namespace per i propri componenti.

Qualsiasi pacchetto Java può essere associato ad un namespace XML annotando il pacchetto con l'annotazione @Namespace. (Le annotazioni a livello pacchetto vengono dichiarate in un file chiamato package-info.java nella directory del pacchetto.) Ecco un esempio tratto dalla demo seampay:

@Namespace(value="http://jboss.com/products/seam/examples/seampay")

package org.jboss.seam.example.seampay;
import org.jboss.seam.annotations.Namespace;

Questo è tutto ciò che bisogna fare per utilizzare lo stile namespace in components.xml! Adesso si può scrivere:


<components xmlns="http://jboss.com/products/seam/components"
            xmlns:pay="http://jboss.com/products/seam/examples/seampay"
            ... >

    <pay:payment-home new-instance="#{newPayment}"
                      created-message="Created a new payment to #{newPayment.payee}" />

    <pay:payment name="newPayment"
                 payee="Somebody"
                 account="#{selectedAccount}"
                 payment-date="#{currentDatetime}"
                 created-date="#{currentDatetime}" />
     ...
</components
>

Oppure:


<components xmlns="http://jboss.com/products/seam/components"
            xmlns:pay="http://jboss.com/products/seam/examples/seampay"
            ... >

    <pay:payment-home>
        <pay:new-instance
>"#{newPayment}"</pay:new-instance>
        <pay:created-message
>Created a new payment to #{newPayment.payee}</pay:created-message>
    </pay:payment-home>
    
    <pay:payment name="newPayment">
        <pay:payee
>Somebody"</pay:payee>
        <pay:account
>#{selectedAccount}</pay:account>
        <pay:payment-date
>#{currentDatetime}</pay:payment-date>
        <pay:created-date
>#{currentDatetime}</pay:created-date>
     </pay:payment>
     ...
</components
>

Questi esempi illustrano i due usi modi d'uso di un elemento con namespace. Nella prima dichiarazione, <pay:payment-home> referenzia il componente paymentHome:

package org.jboss.seam.example.seampay;

...
@Name("paymentHome")
public class PaymentController
    extends EntityHome<Payment>
{
    ... 
}

Il nome dell'elemento è una forma con trattino d'unione del nome del componente. Gli attributi dell'elemento sono la forma con trattino dei nomi delle proprietà.

Nella seconda dichiarazione, l'elemento <pay:payment> fa riferimento alla classe Payment nel pacchetto org.jboss.seam.example.seampay. In questo caso Payment è un entity dichiarato come componente Seam:

package org.jboss.seam.example.seampay;

...
@Name("paymentHome")
public class PaymentController
    extends EntityHome<Payment>
{
    ... 
}

Se si vuole far funzionare la validazione e l'autocompletamento per i componenti definiti dall'utente, occorre uno schema. Seam non fornisce ancora un meccanismo per generare automaticamente uno schema per un set di componenti, così è necessario generarne uno manualmente. Come guida d'esempio si possono usare le definizioni di schema dei pacchetti standard di Seam.

Seam utilizza i seguenti namespace: