Author: pete.muir(a)jboss.org
Date: 2008-05-08 12:49:06 -0400 (Thu, 08 May 2008)
New Revision: 8141
Modified:
trunk/doc/Seam_Reference_Guide/en-US/Concepts.xml
trunk/doc/Seam_Reference_Guide/en-US/Configuration.xml
trunk/doc/Seam_Reference_Guide/en-US/Controls.xml
trunk/doc/Seam_Reference_Guide/en-US/Events.xml
trunk/doc/Seam_Reference_Guide/en-US/Framework.xml
trunk/doc/Seam_Reference_Guide/en-US/Itext.xml
trunk/doc/Seam_Reference_Guide/en-US/Mail.xml
trunk/doc/Seam_Reference_Guide/en-US/Security.xml
trunk/doc/Seam_Reference_Guide/en-US/Testing.xml
Log:
Merge in doc changes from trunk
Modified: trunk/doc/Seam_Reference_Guide/en-US/Concepts.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Concepts.xml 2008-05-08 15:59:42 UTC (rev 8140)
+++ trunk/doc/Seam_Reference_Guide/en-US/Concepts.xml 2008-05-08 16:49:06 UTC (rev 8141)
@@ -268,7 +268,9 @@
</para>
<para>
Since the session context is multithreaded, and often contains volatile
state, session scope
- components are always protected by Seam from concurrent access. Seam
serializes requests to session
+ components are always protected by Seam from concurrent access so long as
the Seam interceptors
+ are not disabled for that component. If interceptors are disabled, then
any thread-safety that is
+ required must be implemented by the component itself. Seam serializes
requests to session
scope session beans and JavaBeans by default (and detects and breaks any
deadlocks that occur). This is
not the default behaviour for application scoped components however,
since application scoped components
do not usually hold volatile state and because synchronization at the
global level is
@@ -357,7 +359,9 @@
the page or stateless contexts.
</para>
<para>
- Concurrent requests to session-scoped stateful session beans are always
serialized by Seam.
+ Concurrent requests to session-scoped stateful session beans are always
serialized by Seam as long
+ as the Seam interceptors are not disabled for the bean.
+
</para>
<para>
Seam stateful session bean components may be instantiated using
<literal>Component.getInstance()</literal>
Modified: trunk/doc/Seam_Reference_Guide/en-US/Configuration.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Configuration.xml 2008-05-08 15:59:42 UTC (rev
8140)
+++ trunk/doc/Seam_Reference_Guide/en-US/Configuration.xml 2008-05-08 16:49:06 UTC (rev
8141)
@@ -68,6 +68,15 @@
<param-value>.xhtml</param-value>
</context-param>]]></programlisting>
+ <para>
+ If you are using facelets in JBoss AS, you'll find that facelets
+ logging is broken. Seam provides a bridge to fix this, to use it
+ copy <literal>lib/interop/jboss-seam-jul.jar</literal> to
+
<literal>$JBOSS_HOME/server/default/deploy/jboss-web.deployer/jsf-libs/</literal>
+ and include the <literal>jboss-seam-ui.jar</literal> in the
+ <literal>WEB-INF/lib</literal> of your application.
+ </para>
+
</sect2>
<sect2>
@@ -959,5 +968,131 @@
</para>
</sect1>
+
+ <sect1>
+ <title>Deploying custom resources</title>
+
+ <para>
+ Seam scans all jars containing
<literal>/seam.properties</literal>,
+ <literal>/META-INF/components.xml</literal> or
<literal>/META-INF/seam.properties</literal>
+ on startup for resources. For example, all classes annotated with
+ <literal>@Name</literal> are registered with Seam as Seam
components.
+ </para>
+
+ <para>
+ You may also want Seam to handle custom resources. A common use case
+ is to handle a specific annotation and Seam provides specific
+ support for this. First, tell Seam which annotations to handle in
+ <literal>/META-INF/seam-deployment.properties</literal>:
+ </para>
+
+ <programlisting><![CDATA[# A colon-separated list of annotation types to
handle
+org.jboss.seam.deployment.annotationTypes=com.acme.Foo:com.acme.Bar]]></programlisting>
+
+ <para>
+ Then, during application startup you can get hold of all classes
+ annotated with <literal>@Foo</literal>:
+ </para>
+
+ <programlisting><![CDATA[@Name("fooStartup")
+@Scope(APPLICATION)
+@Startup
+public class FooStartup {
+ @In("#{deploymentStrategy.annotatedClasses['com.acme.Foo']}")
+ private Set<Class<Object>> fooClasses;
+
+ @In("#{hotDeploymentStrategy.annotatedClasses['com.acme.Foo']}")
+ private Set<Class<Object>> hotFooClasses;
+
+ @Create
+ public void create() {
+ for (Class clazz : fooClasses) {
+ handleClass(clazz);
+ }
+ for (Class clazz : hotFooClasses) {
+ handleClass(clazz);
+ }
+ }
+
+}]]></programlisting>
+
+ <para>
+ You can also handle <emphasis>any</emphasis> resource. For
example,
+ you process any files with the extension
<literal>.foo.xml</literal>.
+ To do this, we need to write a custom deployment handler:
+ </para>
+
+ <programlisting><![CDATA[public class FooDeploymentHandler implements
DeploymentHandler {
+
+ private Set<InputStream> files = new HashSet<InputStream>();
+
+ public String getName() {
+ return "fooDeploymentHandler";
+ }
+
+ public Set<InputStream> getFiles() {
+ return files;
+ }
+
+ public void handle(String name, ClassLoader classLoader) {
+ if (name.endsWith(".foo.xml")) {
+ files.add(classLoader.getResourceAsStream(name));
+ }
+ }
+}]]></programlisting>
+
+ <para>
+ Here we are just building a list of any files with the suffix
+ <literal>.foo.xml</literal>.
+ </para>
+
+ <para>
+ Then, we need to register the deployment handler with Seam. In
+ <literal>/META-INF/seam-deployment.properties</literal>:
+ </para>
+
+ <programlisting><![CDATA[# For standard deployment
+org.jboss.seam.deployment.deploymentHandlers=com.acme.FooDeploymentHandler
+# For hot deployment
+org.jboss.seam.deployment.hotDeploymentHandlers=com.acme.FooDeploymentHandler]]></programlisting>
+
+ <para>
+ You can register multiple deployment handler using a comma
+ separated list.
+ </para>
+
+ <para>
+ Seam uses deployment handlers internally to install components and
+ namespaces, therefore the <literal>handle()</literal> is called
too
+ early in inside Seam bootstrap to normally be useful. However, you
+ can easily access the deployment handler during an
+ <literal>APPLICATION</literal> scoped component's startup:
+ </para>
+
+ <programlisting><![CDATA[@Name("fooStartup")
+@Scope(APPLICATION)
+@Startup
+public class FooStartup {
+
+ @In("#{deploymentStrategy['fooDeploymentHandler']}")
+ private MyDeploymentHandler myDeploymentHandler;
+
+ @In("#{hotDeploymentStrategy['fooDeploymentHandler']}")
+ private MyDeploymentHandler myHotDeploymentHandler;
+
+ @Create
+ public void create() {
+ for (InputStream is : myDeploymentHandler.getFiles()) {
+ handleFooXml(is);
+ }
+ for (InputStream is : myHotDeploymentHandler.getFiles()) {
+ handleFooXml(is);
+ }
+ }
+
+}]]></programlisting>
+
+ </sect1>
+
</chapter>
Modified: trunk/doc/Seam_Reference_Guide/en-US/Controls.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Controls.xml 2008-05-08 15:59:42 UTC (rev 8140)
+++ trunk/doc/Seam_Reference_Guide/en-US/Controls.xml 2008-05-08 16:49:06 UTC (rev 8141)
@@ -96,9 +96,14 @@
<para>
You can specify both <literal>view</literal> and
<literal>action</literal> on <literal><s:link
/></literal>.
- In this case, the action wil be called once the redirect to the
+ In this case, the action will be called once the redirect to the
specified view has occured.
</para>
+
+ <para>
+ The use of action listeners (including the default JSF action
+ listener) is not supported with <literal><s:button
/></literal>.
+ </para>
</section>
@@ -145,6 +150,11 @@
form.</emphasis>
</para>
+ <para>
+ The use of action listeners (including the default JSF action
+ listener) is not supported with <literal><s:link
/></literal>.
+ </para>
+
<para><emphasis>Attributes</emphasis></para>
<itemizedlist>
<listitem>
@@ -299,13 +309,14 @@
<para><emphasis>Description</emphasis></para>
<para>
Assigns an entity converter to the current component. This is
- primarily useful for radio button and dropdown controls.
+ useful for radio button and dropdown controls.
</para>
<para>
- The converter works with any managed entity which has an
- <literal>@Id</literal>
- annotation - either simple or composite.
+ The converter works with any managed entity - either simple or
+ composite. The converter should be able to find the items
+ declared in the JSF controls on form submission, otherwise you
+ will receive a validation error.
</para>
<para><emphasis>Attributes</emphasis></para>
@@ -321,36 +332,59 @@
<para>
If your <emphasis>Managed Persistence Context</emphasis>
isn't
- called <literal>entityManager</literal>, then you need to set
it in
- components.xml:
+ called <literal>entityManager</literal>, then you need to set
it
+ in components.xml:
</para>
- <programlisting role="XML"><![CDATA[<component
name="org.jboss.seam.ui.EntityConverter">
- <property name="entityManager">#{em}</property>
-</component>]]></programlisting>
+ <programlisting><![CDATA[<components
xmlns="http://jboss.com/products/seam/components"
+
xmlns:ui="http://jboss.com/products/seam/ui">
+
+ <ui:entity-loader entity-manager="#{em}"
/>]]></programlisting>
<para>
If you are using a <emphasis>Managed Hibernate
Session</emphasis>
then you need to set it in components.xml:
</para>
- <programlisting role="XML"><![CDATA[<component
name="org.jboss.seam.ui.EntityConverter">
- <property name="session">#{hibernateSession}</property>
-</component>]]></programlisting>
+ <programlisting><![CDATA[<components
xmlns="http://jboss.com/products/seam/components"
+
xmlns:ui="http://jboss.com/products/seam/ui">
+
+ <ui:hibernate-entity-loader />]]></programlisting>
<para>
+ If your <emphasis>Managed Hibernate Session</emphasis>
isn't
+ called <literal>session</literal>, then you need to set it
+ in components.xml:
+ </para>
+
+ <programlisting><![CDATA[<components
xmlns="http://jboss.com/products/seam/components"
+
xmlns:ui="http://jboss.com/products/seam/ui">
+
+ <ui:hibernate-entity-loader session="#{hibernateSession}"
/>]]></programlisting>
+
+ <para>
If you want to use more than one entity manager with the entity
converter, you can create a copy of the entity converter for each
- entity manager in components.xml:
+ entity manager in <literal>components.xml</literal> - note how
+ the entity converter delegates to the entity loader to perform
+ persistence operations:
</para>
- <programlisting role="XML"><![CDATA[<component
name="myEntityConverter"
class="org.jboss.seam.ui.converter.EntityConverter">
- <property name="entityManager">#{em}</property>
-</component>]]></programlisting>
+ <programlisting><![CDATA[<components
xmlns="http://jboss.com/products/seam/components"
+
xmlns:ui="http://jboss.com/products/seam/ui">
+
+ <ui:entity-converter name="standardEntityConverter"
entity-loader="#{standardEntityLoader}" />
+
+ <ui:entity-loader name="standardEntityLoader"
entity-manager="#{standardEntityManager}" />
- <programlisting role="XHTML"><![CDATA[<h:selectOneMenu
value="#{person.continent}">
+ <ui:entity-converter name="restrictedEntityConverter"
entity-loader="#{restrictedEntityLoader}" />
+
+ <ui:entity-loader name="restrictedEntityLoader"
entity-manager="#{restrictedEntityManager}" />]]></programlisting>
+
+ <programlisting><![CDATA[<h:selectOneMenu
value="#{person.continent}">
<s:selectItems value="#{continents.resultList}" var="continent"
label="#{continent.name}" />
- <f:converter converterId="myEntityConverter" />
+ <f:converter converterId="standardEntityConverter" />
</h:selectOneMenu>]]></programlisting>
+
<para><emphasis>Usage</emphasis></para>
<programlisting role="XHTML"><![CDATA[<h:selectOneMenu
value="#{person.continent}" required="true">
@@ -380,9 +414,67 @@
noSelectionLabel="Please select" />
<s:convertEnum />
</h:selectOneMenu>]]></programlisting>
- </section>
+ </section>
<section>
+
<title><literal><s:convertAtomicBoolean></literal></title>
+
+ <para><emphasis>Description</emphasis></para>
+ <para>
+ <literal>javax.faces.convert.Converter</literal> for
+ <literal>java.util.concurrent.atomic.AtomicBoolean</literal>.
+ </para>
+
+ <para><emphasis>Attributes</emphasis></para>
+ <para>
+ None.
+ </para>
+
+ <para><emphasis>Usage</emphasis></para>
+ <programlisting><![CDATA[<h:outputText
value="#{item.valid}">
+ <s:convertAtomicBoolean />
+</h:outputText>]]></programlisting>
+ </section>
+ <section>
+
<title><literal><s:convertAtomicInteger></literal></title>
+
+ <para><emphasis>Description</emphasis></para>
+ <para>
+ <literal>javax.faces.convert.Converter</literal> for
+ <literal>java.util.concurrent.atomic.AtomicInteger</literal>.
+ </para>
+
+ <para><emphasis>Attributes</emphasis></para>
+ <para>
+ None.
+ </para>
+
+ <para><emphasis>Usage</emphasis></para>
+ <programlisting><![CDATA[<h:outputText
value="#{item.id}">
+ <s:convertAtomicInteger />
+</h:outputText>]]></programlisting>
+ </section>
+ <section>
+
<title><literal><s:convertAtomicLong></literal></title>
+
+ <para><emphasis>Description</emphasis></para>
+ <para>
+ <literal>javax.faces.convert.Converter</literal> for
+ <literal>java.util.concurrent.atomic.AtomicLong</literal>.
+ </para>
+
+ <para><emphasis>Attributes</emphasis></para>
+ <para>
+ None.
+ </para>
+
+ <para><emphasis>Usage</emphasis></para>
+ <programlisting><![CDATA[<h:outputText
value="#{item.id}">
+ <s:convertAtomicLong />
+</h:outputText>]]></programlisting>
+ </section>
+
+ <section>
<title><literal><s:validate></literal></title>
<para><emphasis>Description</emphasis></para>
Modified: trunk/doc/Seam_Reference_Guide/en-US/Events.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Events.xml 2008-05-08 15:59:42 UTC (rev 8140)
+++ trunk/doc/Seam_Reference_Guide/en-US/Events.xml 2008-05-08 16:49:06 UTC (rev 8141)
@@ -885,7 +885,7 @@
</filter-mapping>]]></programlisting>
<para>
- You may also need to disable Facelets development mode in
<literal>web.xml</literal> and
+ You need to disable Facelets development mode in
<literal>web.xml</literal> and
Seam debug mode in <literal>components.xml</literal> if you want
your exception handlers
to fire.
</para>
@@ -997,7 +997,7 @@
<para>
<literal>org.jboss.seam.handledException</literal> holds the
nested exception that
was actually handled by an exception handler. The outermost (wrapper)
exception is
- also available, as <literal>org.jboss.seam.exception</literal>.
+ also available, as
<literal>org.jboss.seam.caughtException</literal>.
</para>
<section>
Modified: trunk/doc/Seam_Reference_Guide/en-US/Framework.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Framework.xml 2008-05-08 15:59:42 UTC (rev 8140)
+++ trunk/doc/Seam_Reference_Guide/en-US/Framework.xml 2008-05-08 16:49:06 UTC (rev 8141)
@@ -55,8 +55,7 @@
your taste, you can use extension instead:
</para>
- <programlisting role="JAVA"><![CDATA[
-@Name("personHome")
+ <programlisting
role="JAVA"><![CDATA[@Name("personHome")
public class PersonHome extends EntityHome<Person> {
@In EntityManager personDatabase;
Modified: trunk/doc/Seam_Reference_Guide/en-US/Itext.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Itext.xml 2008-05-08 15:59:42 UTC (rev 8140)
+++ trunk/doc/Seam_Reference_Guide/en-US/Itext.xml 2008-05-08 16:49:06 UTC (rev 8141)
@@ -95,6 +95,12 @@
and
<literal>attachment</literal>, which indicates that the document should be
treated as a download.
The default value is
<literal>inline</literal>.</para>
</listitem>
+ <listitem>
+ <para>
+ <literal>fileName</literal>
— For attachments, this value
+ overrides the downloaded file name.
+ </para>
+ </listitem>
</itemizedlist>
Modified: trunk/doc/Seam_Reference_Guide/en-US/Mail.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Mail.xml 2008-05-08 15:59:42 UTC (rev 8140)
+++ trunk/doc/Seam_Reference_Guide/en-US/Mail.xml 2008-05-08 16:49:06 UTC (rev 8141)
@@ -567,6 +567,11 @@
<literal><h:graphicImage></literal> in your
emails.
</para>
</listitem>
+ <listitem>
+ <para>
+ <literal>messageId</literal> — Sets the Message-ID
explicitly
+ </para>
+ </listitem>
</itemizedlist>
</listitem>
</varlistentry>
Modified: trunk/doc/Seam_Reference_Guide/en-US/Security.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Security.xml 2008-05-08 15:59:42 UTC (rev 8140)
+++ trunk/doc/Seam_Reference_Guide/en-US/Security.xml 2008-05-08 16:49:06 UTC (rev 8141)
@@ -1622,5 +1622,919 @@
}]]></programlisting>
</sect1>
+
+ <sect1>
+ <title>Identity Management</title>
+ <para>
+ Seam Security provides an optional identity management API, which offers the
following features:
+ </para>
+
+ <itemizedlist>
+ <listitem>
+ <para>
+ User management - the ability to create, delete and modify user accounts and
their role memberships.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Authentication of users without the need for writing an Authenticator
component.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ A hierarchical role/group membership structure, allowing roles to be members of
other roles.
+ </para>
+ </listitem>
+ <listitem>
+ <para>
+ Pluggable identity store, allowing the developer to choose their security
provider, whether it be
+ JPA, LDAP, Kerberos, etc.
+ </para>
+ </listitem>
+ </itemizedlist>
+
+ <para>
+ The core of the identity management API is the
<literal>IdentityManager</literal> component. Before it can be
+ used however, it must be configured with an
<literal>IdentityStore</literal> implementation. The
+ <literal>IdentityStore</literal> does the actual work of interacting
with the underlying security provider,
+ whatever it may be.
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-identitymanager.png"
align="center"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="../shared/images/security-identitymanager.png"
align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <sect2>
+ <title>Configuration</title>
+
+ <para>
+ Configuration of the <literal>IdentityManager</literal> is extremely
simple, requiring only an
+ <literal>IdentityStore</literal> to be configured in
<literal>components.xml</literal>.
+ The identity management namespace is
<
literal>http://jboss.com/products/seam/security/management</literal...
+ and its schema location is
<
literal>http://jboss.com/products/seam/identity-management-2.1.xsd<...;.
+ Here's a simple example showing the configuration of a
<literal>JPAIdentityStore</literal> - for the
+ <literal>IdentityManager</literal> to use it, it must be named
<literal>identityStore</literal>:
+ </para>
+
+ <programlisting><![CDATA[
+ <identity-management:jpa-identity-store name="identityStore"
account-class="com.acme.UserAccount"/>
+ ]]></programlisting>
+ </sect2>
+
+ <sect2>
+ <title>JPAIdentityStore</title>
+
+ <para>
+ <literal>JPAIdentityStore</literal> is an
<literal>IdentityStore</literal> implementation that uses
+ JPA as its underlying security provider. User accounts and their role
memberships are stored in a
+ self-referencing database table, for which the corresponding entity bean must
extend
+ <literal>org.jboss.seam.security.management.UserAccount</literal> to
provide the following properties:
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-useraccount.png"
align="center"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="../shared/images/security-useraccount.png"
align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <para>
+ To provide a complete example, here's what the actual database tables may
look like:
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-useraccountschema.png"
align="center"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata
fileref="../shared/images/security-useraccountschema.png"
align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <para>
+ And an example of the corresponding entity bean:
+ </para>
+
+ <programlisting><![CDATA[@Entity @Table(name = "USER_ACCOUNT")
+public class UserAccount extends org.jboss.seam.security.management.UserAccount
+ implements Serializable
+{
+ private Integer accountId;
+ private String username;
+ private String passwordHash;
+ private boolean enabled;
+ private AccountType accountType;
+ private Set<UserAccount> memberships;
+
+ @Id @GeneratedValue public Integer getAccountId() { return accountId; }
+ public void setAccountId(Integer accountId) { this.accountId = accountId; }
+
+ @NotNull @Override public String getUsername() { return username; }
+ @Override public void setUsername(String username) { this.username = username; }
+
+ @Override public String getPasswordHash() { return passwordHash; }
+ @Override public void setPasswordHash(String passwordHash) { this.passwordHash =
passwordHash; }
+
+ @Override public AccountType getAccountType() { return accountType; }
+ @Override public void setAccountType(AccountType accountType) { this.accountType =
accountType; }
+
+ @Override public boolean isEnabled() { return enabled; }
+ @Override public void setEnabled(boolean enabled) { this.enabled = enabled; }
+
+ @ManyToMany(targetEntity = MemberAccount.class) @JoinTable(name =
"ACCOUNT_MEMBERSHIP",
+ joinColumns = @JoinColumn(name = "ACCOUNT_ID"),
+ inverseJoinColumns = @JoinColumn(name = "MEMBER_OF"))
+ @Override public Set<UserAccount> getMemberships() { return memberships; }
+ @Override public void setMemberships(Set<UserAccount> memberships) {
this.memberships = memberships; }}]]></programlisting>
+
+ <para>
+ In the above example, the implementation of
<literal>UserAccount</literal> is self-referencing
+ in that it has a many-to-many relationship with itself via its
<literal>memberships</literal>
+ property. To keep the model simple, both user accounts and roles are persisted
as
+ <literal>UserAccount</literal>s, with the
<literal>accountType</literal> property acting as the
+ discriminator between the two. With this model, roles can be members of other
roles, making it
+ possible to define complex role membership hierarchies.
+ </para>
+
+ <para>
+ Once the <literal>UserAccount</literal> implementation has been
created, the <literal>JPAIdentityStore</literal>
+ must be configured to use that implementation any time it performs an identity
management operation.
+ This is done by specifying the <literal>account-class</literal>
property in <literal>components.xml</literal>.
+ In the following example, it is configured as
<literal>com.acme.UserAccount</literal>:
+ </para>
+
+ <programlisting><![CDATA[
+ <identity-management:jpa-identity-store name="identityStore"
account-class="com.acme.UserAccount"/>]]></programlisting>
+
+ <para>
+ Please note that this is a required parameter, and must always be specified when
using the
+ <literal>JPAIdentityStore</literal>.
+ </para>
+
+ <sect3>
+ <title>Password hashing</title>
+
+ <para>
+ The <literal>JPAIdentityStore</literal> stores a salted hash of
the user's password, using the username
+ as the source material for salt generation. This guarantees that two users
with the same password will
+ still have different password hashes. The method for generating a password
hash is listed here for
+ convenience - it may be useful for generating password hashes for inclusion
in DML scripts, etc:
+ </para>
+
+ <programlisting><![CDATA[
+ public String hashPassword(String password, String saltPhrase)
+ {
+ try {
+ MessageDigest md = MessageDigest.getInstance("MD5");
+
+ md.update(saltPhrase.getBytes());
+ byte[] salt = md.digest();
+
+ md.reset();
+ md.update(password.getBytes("UTF-8"));
+ md.update(salt);
+
+ byte[] raw = md.digest();
+
+ return new String(Hex.encodeHex(raw));
+ }
+ catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+ }
+ ]]></programlisting>
+ </sect3>
+
+ </sect2>
+
+ <sect2>
+ <title>Authentication with the Identity Management API</title>
+
+ <para>
+ To authenticate using the Identity Management API, it is as simple as not
specifying the
+ <literal>authenticate-method</literal> property for the
<literal>Identity</literal> component.
+ If no <literal>authenticate-method</literal> is specified, then by
default the authentication
+ process (controlled by <literal>SeamLoginModule</literal>) will
attempt to authenticate using
+ <literal>IdentityManager</literal>'s
<literal>authenticate()</literal> method, and no
+ Authenticator component is necessary.
+ </para>
+ </sect2>
+
+ <sect2>
+ <title>Using the IdentityManager API</title>
+
+ <para>
+ The <literal>IdentityManager</literal> can be accessed either by
injecting it into your Seam
+ component as follows:
+ </para>
+
+ <programlisting><![CDATA[ @In IdentityManager
identityManager;]]></programlisting>
+
+ <para>
+ or by accessing it through its static <literal>instance()</literal>
method:
+ </para>
+
+ <programlisting><![CDATA[ IdentityManager identityManager =
IdentityManager.instance();]]></programlisting>
+
+ <para>
+ The following table describes each of the methods that
<literal>IdentityManager</literal> provides:
+ </para>
+
+ <table>
+ <title>Identity Management API</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="1*" />
+ <colspec colnum="2" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Method</para>
+ </entry>
+ <entry align="center">
+ <para>Returns</para>
+ </entry>
+ <entry align="center">
+ <para>Description</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+
+ <row>
+ <entry>
+ <para>
+ <literal>createUser(String name, String
password)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Creates a new user account, with the specified name and password.
Returns <literal>true</literal>
+ if successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>deleteUser(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Deletes the user account with the specified name. Returns
<literal>true</literal>
+ if successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>createRole(String role)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Creates a new role, with the specified name. Returns
<literal>true</literal>
+ if successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>deleteRole(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Deletes the role with the specified name. Returns
<literal>true</literal>
+ if successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>enableUser(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Enables the user account with the specified name. Accounts that are
not enabled are
+ not able to authenticate. Returns <literal>true</literal>
if successful, or
+ <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>disableUser(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Disables the user account with the specified name. Returns
<literal>true</literal> if
+ successful, or <literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>changePassword(String name, String
password)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Changes the password for the user account with the specified name.
Returns
+ <literal>true</literal> if successful, or
<literal>false</literal> if not.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>isUserEnabled(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns <literal>true</literal> if the specified user
account is enabled, or
+ <literal>false</literal> if it isn't.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>grantRole(String name, String role)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Grants the specified role to the specified user or role. The role must
already exist for it to
+ be granted. Returns <literal>true</literal> if the role is
successfully granted, or
+ <literal>false</literal> if it is already granted to the
user.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>revokeRole(String name, String role)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Revokes the specified role from the specified user or role. Returns
<literal>true</literal>
+ if the specified user is a member of the role and it is successfully
revoked, or
+ <literal>false</literal> if the user is not a member of the
role.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>userExists(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns <literal>true</literal> if the specified user
exists, or <literal>false</literal>
+ if it doesn't.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>listUsers()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of all user names, sorted in alpha-numeric order.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>listUsers(String filter)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of all user names filtered by the specified filter
parameter, sorted in alpha-numeric order.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>listRoles()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of all role names.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>getGrantedRoles(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of the names of all the roles explicitly granted to the
specified user name.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>getImpliedRoles(String name)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>List</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Returns a list of the names of all the roles implicitly granted to the
specified user name.
+ Implicitly granted roles include those that are not directly granted to
a user, rather they are
+ granted to the roles that the user is a member of. For example, is the
<literal>admin</literal>
+ role is a member of the <literal>user</literal> role, and a
user is a member of the <literal>admin</literal>
+ role, then the implied roles for the user are both the
<literal>admin</literal>, and <literal>user</literal>
+ roles.
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>authenticate(String name, String
password)</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>boolean</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ Authenticates the specified username and password using the configured
Identity Store. Returns
+ <literal>true</literal> if successful or
<literal>false</literal> if authentication failed.
+ Successful authentication implies nothing beyond the return value of
the method. It does not
+ change the state of the <literal>Identity</literal>
component - to perform a proper Seam login the
+ <literal>Identity.login()</literal> must be used instead.
+ </para>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ Using the Identity Management API requires that the calling user has the
appropriate authorization to invoke
+ its methods. The following table describes the permission requirements for each
of the methods in
+ <literal>IdentityManager</literal>.
+ </para>
+
+ <table>
+ <title>Identity Management Security Permissions</title>
+
+ <tgroup cols="2">
+ <colspec colnum="1" colwidth="1*" />
+ <colspec colnum="2" colwidth="3*" />
+
+ <thead>
+ <row>
+ <entry align="center">
+ <para>Method</para>
+ </entry>
+ <entry align="center">
+ <para>Permission Name</para>
+ </entry>
+ <entry align="center">
+ <para>Permission Action</para>
+ </entry>
+ </row>
+ </thead>
+
+ <tbody>
+ <row>
+ <entry>
+ <para>
+ <literal>createUser()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>create</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>deleteUser()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>delete</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>createRole()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>create</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>deleteRole()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>delete</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>enableUser()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>disableUser()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>changePassword()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>isUserEnabled()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>read</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>grantRole()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>revokeRole()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>update</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>userExists()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>read</literal>
+ </para>
+ </entry>
+ </row>
+
+ <row>
+ <entry>
+ <para>
+ <literal>listUsers()</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>seam.account</literal>
+ </para>
+ </entry>
+ <entry>
+ <para>
+ <literal>read</literal>
+ </para>
+ </entry>
+ </row>
+
+ </tbody>
+ </tgroup>
+ </table>
+
+ <para>
+ The following code listing provides an example set of security rules that grants
access to all
+ Identity Management-related methods to members of the
<literal>admin</literal> role:
+ </para>
+
+ <programlisting><![CDATA[rule CreateAccount
+ no-loop
+ activation-group "permissions"
+when
+ check: PermissionCheck(name == "seam.account", action == "create",
granted == false)
+ Role(name == "admin")
+then
+ check.grant();
+end
+
+rule ReadAccount
+ no-loop
+ activation-group "permissions"
+when
+ check: PermissionCheck(name == "seam.account", action == "read",
granted == false)
+ Role(name == "admin")
+then
+ check.grant();
+end
+
+rule UpdateAccount
+ no-loop
+ activation-group "permissions"
+when
+ check: PermissionCheck(name == "seam.account", action == "update",
granted == false)
+ Role(name == "admin")
+then
+ check.grant();
+end
+
+rule DeleteAccount
+ no-loop
+ activation-group "permissions"
+when
+ check: PermissionCheck(name == "seam.account", action == "delete",
granted == false)
+ Role(name == "admin")
+then
+ check.grant();
+end]]></programlisting>
+
+ </sect2>
+
+ <sect2>
+ <title>Seam-gen and Identity Management</title>
+
+ <para>
+ When creating a new project using seam-gen (see <xref
linkend="gettingstarted"/>), by default the
+ <literal>IdentityManager</literal> will be configured with a
<literal>JPAIdentityStore</literal>
+ and a <literal>UserAccount</literal> implementation will be generated
as part of the new project.
+ In addition to this, the project will include the following user management
screens, allowing
+ new users to be created, roles assigned, etc:
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-usermanager1.png"
align="center"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="../shared/images/security-usermanager1.png"
align="center"/>
+ </imageobject>
+ </mediaobject>
+
+ <para>
+ The user detail screen:
+ </para>
+
+ <mediaobject>
+ <imageobject role="fo">
+ <imagedata fileref="images/security-usermanager2.png"
align="center"/>
+ </imageobject>
+ <imageobject role="html">
+ <imagedata fileref="../shared/images/security-usermanager2.png"
align="center"/>
+ </imageobject>
+ </mediaobject>
+
+
+ </sect2>
+
+ </sect1>
+
</chapter>
Modified: trunk/doc/Seam_Reference_Guide/en-US/Testing.xml
===================================================================
--- trunk/doc/Seam_Reference_Guide/en-US/Testing.xml 2008-05-08 15:59:42 UTC (rev 8140)
+++ trunk/doc/Seam_Reference_Guide/en-US/Testing.xml 2008-05-08 16:49:06 UTC (rev 8141)
@@ -530,10 +530,14 @@
</para>
<para>
- You need to provide a dataset for DBUnit. IMPORTANT NOTE: DBUnit supports
two
- formats for dataset files, flat and XML. Seam's DBUnitSeamTest assumes
the flat
- format is used, so please ensure that your dataset is in this format also.
+ You need to provide a dataset for DBUnit.
</para>
+
+ <caution>
+ DBUnit supports two formats for dataset files, flat and XML. Seam's
+ DBUnitSeamTest assumes the flat format is used, so make sure that
+ your dataset is in this format.
+ </caution>
<programlisting role="XML"><![CDATA[<dataset>
@@ -581,11 +585,30 @@
<![CDATA[<parameter name="datasourceJndiName"
value="java:/seamdiscsDatasource"/>]]>
</programlisting>
- <para>
- DBUnitSeamTest only works out of the box with HSQL as a datasource.
- If you want to use another database, then you'll need to implement
- some extra methods. Read the javadoc on
- <literal>DBUnitSeamTest</literal> for more.
+ <para>
+ DBUnitSeamTest has support for MySQL and HSQL - you need to tell it
+ which database is being used:
+ </para>
+
+ <programlisting><![CDATA[<parameter name="database"
value="HSQL" />]]></programlisting>
+
+ <para>
+ It also allows you to insert binary data into the test data set (n.b.
+ this is untested on Windows). You need to tell it where to locate
+ these resources:
+ </para>
+
+ <programlisting><![CDATA[<parameter name="binaryDir"
value="images/" />]]></programlisting>
+
+ <para>
+ You <emphasis>must</emphasis> specify these three parameters in
your
+ <literal>testng.xml</literal>.
+ </para>
+
+ <para>
+ If you want to use DBUnitSeamTest with another database, you'll need
+ to implement some methods. Read the javadoc of
+ <literal>AbstractDBUnitSeamTest</literal> for more.
</para>
</section>
@@ -593,9 +616,9 @@
<section id="testing.mail">
<title>Integration Testing Seam Mail</title>
- <para>
+ <caution>
Warning! This feature is still under development.
- </para>
+ </caution>
<para>
It's very easy to integration test your Seam Mail: