Author: ddam
Date: Tue Sep 30 09:43:30 2008
New Revision: 700523
URL:
http://svn.apache.org/viewvc?rev=700523&view=revLog:
- implemented updating entity + tests
- renamed baseDn to searchDn in LdapEntityDaoConfiguration, reused baseDN for the ldap server's base DN
Modified:
portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/EntityDAO.java
portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/LDAPEntityDAOConfiguration.java
portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/impl/SpringLDAPEntityDAO.java
portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup1/AbstractSetup1LDAPTest.java
portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup1/UserTests.java
portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup2/AbstractSetup2LDAPTest.java
portals/jetspeed-2/portal/branches/security-refactoring/jetspeed-portal-resources/src/main/resources/assembly/security-ldap.xml
portals/jetspeed-2/portal/branches/security-refactoring/jetspeed-portal-resources/src/main/resources/conf/jetspeed/jetspeed.properties
Modified: portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/EntityDAO.java
URL:
http://svn.apache.org/viewvc/portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/EntityDAO.java?rev=700523&r1=700522&r2=700523&view=diff==============================================================================
--- portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/EntityDAO.java (original)
+++ portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/EntityDAO.java Tue Sep 30 09:43:30 2008
@@ -78,7 +78,13 @@
void addEntity(Entity entity);
void removeEntity(Entity entity);
-
+
+ void update(Entity entity, Entity parentEntity);
+
+ void addEntity(Entity entity, Entity parentEntity);
+
+ void removeEntity(Entity entity, Entity parentEntity);
+
EntityFactory getEntityFactory();
}
Modified: portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/LDAPEntityDAOConfiguration.java
URL:
http://svn.apache.org/viewvc/portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/LDAPEntityDAOConfiguration.java?rev=700523&r1=700522&r2=700523&view=diff==============================================================================
--- portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/LDAPEntityDAOConfiguration.java (original)
+++ portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/LDAPEntityDAOConfiguration.java Tue Sep 30 09:43:30 2008
@@ -30,6 +30,8 @@
{
private String baseDN;
+
+ private String searchDN;
private Filter baseFilter;
@@ -48,13 +50,23 @@
{
this.baseDN = baseDN;
}
+
+ public String getSearchDN()
+ {
+ return searchDN;
+ }
+
+ public void setSearchDN(String searchDN)
+ {
+ this.searchDN = searchDN;
+ }
- public Filter getBaseFilter()
+ public Filter getSearchFilter()
{
return baseFilter;
}
- public void setBaseFilter(Filter baseFilter)
+ public void setSearchFilter(Filter baseFilter)
{
this.baseFilter = baseFilter;
}
Modified: portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/impl/SpringLDAPEntityDAO.java
URL:
http://svn.apache.org/viewvc/portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/impl/SpringLDAPEntityDAO.java?rev=700523&r1=700522&r2=700523&view=diff==============================================================================
--- portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/impl/SpringLDAPEntityDAO.java (original)
+++ portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/main/java/org/apache/jetspeed/security/mapping/ldap/dao/impl/SpringLDAPEntityDAO.java Tue Sep 30 09:43:30 2008
@@ -20,6 +20,10 @@
import java.util.Collection;
import java.util.Iterator;
+import javax.naming.Name;
+import javax.naming.directory.BasicAttribute;
+import javax.naming.directory.DirContext;
+import javax.naming.directory.ModificationItem;
import javax.naming.directory.SearchControls;
import org.apache.commons.lang.StringUtils;
@@ -30,8 +34,11 @@
import org.apache.jetspeed.security.mapping.ldap.dao.LDAPEntityDAOConfiguration;
import org.apache.jetspeed.security.mapping.ldap.dao.SearchUtil;
import org.apache.jetspeed.security.mapping.ldap.filter.SimpleFilter;
+import org.apache.jetspeed.security.mapping.model.Attribute;
+import org.apache.jetspeed.security.mapping.model.AttributeDef;
import org.apache.jetspeed.security.mapping.model.Entity;
import org.springframework.ldap.core.ContextMapper;
+import org.springframework.ldap.core.DirContextOperations;
import org.springframework.ldap.core.DistinguishedName;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.ldap.filter.EqualsFilter;
@@ -86,9 +93,9 @@
idFilter.or(new EqualsFilter(idAttr, id));
}
Filter combinedFilter = null;
- if (configuration.getBaseFilter() != null)
+ if (configuration.getSearchFilter() != null)
{
- combinedFilter = SearchUtil.andFilters(idFilter, configuration.getBaseFilter());
+ combinedFilter = SearchUtil.andFilters(idFilter, configuration.getSearchFilter());
}
else
{
@@ -103,9 +110,8 @@
for (Iterator<String> iterator = internalIds.iterator(); iterator.hasNext();)
{
String internalId = (String) iterator.next();
- DistinguishedName principalDN = new DistinguishedName(internalId);
- principalDN.removeFirst();
- internalId =principalDN.toString();
+ DistinguishedName principalDN = getRelativeDN(internalId);
+ internalId = principalDN.toString();
Entity resultEntity = (Entity) ldapTemplate.lookup(internalId, getContextMapper());
if (resultEntity != null)
{
@@ -114,19 +120,25 @@
}
return resultSet;
}
+
+ protected DistinguishedName getRelativeDN(String fullDN){
+ DistinguishedName principalDN = new DistinguishedName(fullDN);
+ if (configuration.getBaseDN() != null && configuration.getBaseDN().length() > 0){
+ principalDN.removeFirst(new DistinguishedName(configuration.getBaseDN()));
+ }
+ return principalDN;
+ }
- @SuppressWarnings("unchecked")
- public Collection<Entity> getEntities(Filter filter)
- {
- if (configuration.getBaseFilter() != null)
+ protected String createSearchFilter(Filter filter){
+ if (configuration.getSearchFilter() != null)
{
if (filter == null)
{
- filter = configuration.getBaseFilter();
+ filter = configuration.getSearchFilter();
}
else
{
- filter = SearchUtil.andFilters(configuration.getBaseFilter(), filter);
+ filter = SearchUtil.andFilters(configuration.getSearchFilter(), filter);
}
}
String filterStr = filter.toString();
@@ -134,17 +146,41 @@
{
filterStr = "(objectClass=*)"; // trivial search query
}
- return (Collection<Entity>) ldapTemplate.search(configuration.getBaseDN(), filterStr, SearchControls.SUBTREE_SCOPE, getContextMapper());
+ return filterStr;
+ }
+
+ @SuppressWarnings("unchecked")
+ public Collection<Entity> getEntities(Filter filter)
+ {
+ String filterStr = createSearchFilter(filter);
+ return (Collection<Entity>) ldapTemplate.search(configuration.getSearchDN(), filterStr, SearchControls.SUBTREE_SCOPE, getContextMapper());
}
public Collection<Entity> getAllEntities()
{
- final String finalFilter = configuration.getBaseFilter() != null ? configuration.getBaseFilter().encode() : "(objectClass=*)";
+ final String finalFilter = configuration.getSearchFilter() != null ? configuration.getSearchFilter().encode() : "(objectClass=*)";
return getEntities(new SimpleFilter(finalFilter));
}
public void update(Entity entity)
{
+ String internalIdStr = entity.getInternalId();
+ if (internalIdStr == null){
+ Entity ldapEntity = getEntity(entity.getId());
+ if (ldapEntity == null || ldapEntity.getInternalId() == null){
+ // TODO throw exception
+ return;
+ }
+ internalIdStr = entity.getInternalId();
+ }
+ Name dn=getRelativeDN(internalIdStr);
+ DirContextOperations dirCtxOps = ldapTemplate.lookupContext(dn);
+ if (dirCtxOps == null){
+ // TODO throw exception
+ return;
+ }
+ Collection<ModificationItem> modItems = getModItems(entity,dirCtxOps);
+ ldapTemplate.modifyAttributes(dn, modItems.toArray(new ModificationItem[]{}));
}
public void addEntity(Entity entity)
@@ -155,14 +191,25 @@
{
}
- public LDAPEntityDAOConfiguration getConfiguration()
+
+ public void addEntity(Entity entity, Entity parentEntity)
{
- return configuration;
+
}
- protected Filter createFilterForIdSearch(String entityId)
+ public void removeEntity(Entity entity, Entity parentEntity)
{
- return SearchUtil.constructMatchingFieldsFilter(configuration.getBaseFilter(), new String[] { configuration.getLdapIdAttribute(), entityId });
+
+ }
+
+ public void update(Entity entity, Entity parentEntity)
+ {
+
+ }
+
+ public LDAPEntityDAOConfiguration getConfiguration()
+ {
+ return configuration;
}
public ContextMapper getContextMapper()
@@ -184,4 +231,68 @@
{
this.contextMapper = contextMapper;
}
+
+ protected boolean setNamingAttribute(Attribute entityAttr, DirContextOperations dirCtxOps){
+ boolean attrAdded = false;
+ if (entityAttr != null){
+ AttributeDef attrDef = entityAttr.getDefinition();
+ if (attrDef.isMultiValue()){
+ Collection<String> values = entityAttr.getValues();
+ if (values != null){
+ dirCtxOps.setAttributeValues(attrDef.getName(),values.toArray());
+ attrAdded = true;
+ }
+ } else {
+ String value = entityAttr.getValue();
+ if (value != null){
+ dirCtxOps.setAttributeValue(attrDef.getName(),value);
+ attrAdded = true;
+ }
+ }
+ }
+ return attrAdded;
+ }
+
+ protected Collection<ModificationItem> getModItems(Entity entity, DirContextOperations dirCtxOps){
+ Collection<ModificationItem> modItems = new ArrayList<ModificationItem>();
+
+ for(AttributeDef attrDef : configuration.getAttributeDefinitions()){
+ if (!attrDef.getName().equals(configuration.getLdapIdAttribute())){
+ Attribute entityAttr = entity.getAttribute(attrDef.getName());
+ boolean attrAdded = false;
+ if (entityAttr != null){
+ if (attrDef.isMultiValue()){
+ Collection<String> values = entityAttr.getValues();
+ if (values != null){
+ javax.naming.directory.Attribute namingAttr = new BasicAttribute(entityAttr.getName(), entityAttr.getValues().toArray());
+ modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,namingAttr));
+ attrAdded = true;
+ }
+ } else {
+ String value = entityAttr.getValue();
+ if (value != null){
+ javax.naming.directory.Attribute namingAttr = new BasicAttribute(entityAttr.getName(), entityAttr.getValue());
+ modItems.add(new ModificationItem(DirContext.REPLACE_ATTRIBUTE,namingAttr));
+ attrAdded = true;
+ }
+ }
+ }
+ if (!attrAdded){
+ // entity attribute not added, so remove it if present in ldap.
+ Object namingAttrValue = dirCtxOps.getObjectAttribute(attrDef.getName());
+ if (namingAttrValue != null){
+ modItems.add(new ModificationItem(DirContext.REMOVE_ATTRIBUTE,new BasicAttribute(attrDef.getName(), namingAttrValue)));
+ }
+ }
+ }
+ }
+ return modItems;
+ }
+
+ protected Filter createFilterForIdSearch(String entityId)
+ {
+ return SearchUtil.constructMatchingFieldsFilter(configuration.getSearchFilter(), new String[] { configuration.getLdapIdAttribute(), entityId });
+ }
+
+
}
Modified: portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup1/AbstractSetup1LDAPTest.java
URL:
http://svn.apache.org/viewvc/portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup1/AbstractSetup1LDAPTest.java?rev=700523&r1=700522&r2=700523&view=diff==============================================================================
--- portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup1/AbstractSetup1LDAPTest.java (original)
+++ portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup1/AbstractSetup1LDAPTest.java Tue Sep 30 09:43:30 2008
@@ -86,9 +86,10 @@
userAttrDefs.add(GIVEN_NAME_DEF);
userSearchConfig = new LDAPEntityDAOConfiguration();
- userSearchConfig.setBaseDN("");
+ userSearchConfig.setBaseDN("o=sevenSeas");
+ userSearchConfig.setSearchDN("");
userSearchConfig
- .setBaseFilter(new SimpleFilter("(objectClass=person)"));
+ .setSearchFilter(new SimpleFilter("(objectClass=person)"));
userSearchConfig.setLdapIdAttribute("uid");
userSearchConfig.setAttributeDefinitions(userAttrDefs);
userSearchConfig.setEntityType("user");
@@ -103,8 +104,9 @@
roleAttrDefs.add(DESCRIPTION_ATTR_DEF);
LDAPEntityDAOConfiguration roleSearchConfig = new LDAPEntityDAOConfiguration();
- roleSearchConfig.setBaseDN("");
- roleSearchConfig.setBaseFilter(new SimpleFilter(
+ roleSearchConfig.setBaseDN("o=sevenSeas");
+ roleSearchConfig.setSearchDN("");
+ roleSearchConfig.setSearchFilter(new SimpleFilter(
"(objectClass=groupOfUniqueNames)"));
roleSearchConfig.setLdapIdAttribute("cn");
roleSearchConfig.setAttributeDefinitions(roleAttrDefs);
Modified: portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup1/UserTests.java
URL:
http://svn.apache.org/viewvc/portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup1/UserTests.java?rev=700523&r1=700522&r2=700523&view=diff==============================================================================
--- portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup1/UserTests.java (original)
+++ portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup1/UserTests.java Tue Sep 30 09:43:30 2008
@@ -59,6 +59,35 @@
"jsmith", resultSet);
}
+ public void testUpdateEntity() throws Exception
+ {
+ EntityImpl sampleUser = new EntityImpl("user", "jsmith", userAttrDefs);
+ sampleUser
+ .setInternalId("cn=jsmith, ou=People, ou=OrgUnit3, o=sevenSeas");
+ sampleUser.setAttribute(GIVEN_NAME_DEF.getName(), "Joe Smith");
+ sampleUser.setAttribute(UID_DEF.getName(), "jsmith");
+ sampleUser.setAttribute(CN_DEF.getName(), "jsmith");
+ basicTestCases.testFetchSingleEntity(entityManager, sampleUser);
+
+ // test attribute modification
+ sampleUser.setAttribute(GIVEN_NAME_DEF.getName(), "Joe Smith modified");
+ entityManager.update(sampleUser);
+
+
+ basicTestCases.testFetchSingleEntity(entityManager, sampleUser);
+
+ // test attribute removal
+ sampleUser = new EntityImpl("user", "jsmith", userAttrDefs);
+ sampleUser
+ .setInternalId("cn=jsmith, ou=People, ou=OrgUnit3, o=sevenSeas");
+ sampleUser.setAttribute(UID_DEF.getName(), "jsmith");
+ sampleUser.setAttribute(CN_DEF.getName(), "jsmith");
+
+ entityManager.update(sampleUser);
+
+ basicTestCases.testFetchSingleEntity(entityManager, sampleUser);
+ }
+
@Override
protected void internaltearDown() throws Exception
{
Modified: portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup2/AbstractSetup2LDAPTest.java
URL:
http://svn.apache.org/viewvc/portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup2/AbstractSetup2LDAPTest.java?rev=700523&r1=700522&r2=700523&view=diff==============================================================================
--- portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup2/AbstractSetup2LDAPTest.java (original)
+++ portals/jetspeed-2/portal/branches/security-refactoring/components/jetspeed-security/src/test/java/org/apache/jetspeed/security/mapping/ldap/setup2/AbstractSetup2LDAPTest.java Tue Sep 30 09:43:30 2008
@@ -88,8 +88,9 @@
userAttrDefs.add(J2_ROLE_DEF);
userSearchConfig = new LDAPEntityDAOConfiguration();
- userSearchConfig.setBaseDN("");
- userSearchConfig.setBaseFilter(new SimpleFilter(
+ userSearchConfig.setBaseDN("o=sevenSeas");
+ userSearchConfig.setSearchDN("");
+ userSearchConfig.setSearchFilter(new SimpleFilter(
"(objectClass=jetspeed-2-user)"));
userSearchConfig.setLdapIdAttribute("uid");
userSearchConfig.setAttributeDefinitions(userAttrDefs);
@@ -105,8 +106,9 @@
roleAttrDefs.add(DESCRIPTION_ATTR_DEF);
LDAPEntityDAOConfiguration roleSearchConfig = new LDAPEntityDAOConfiguration();
- roleSearchConfig.setBaseDN("");
- roleSearchConfig.setBaseFilter(new SimpleFilter(
+ roleSearchConfig.setBaseDN("o=sevenSeas");
+ roleSearchConfig.setSearchDN("");
+ roleSearchConfig.setSearchFilter(new SimpleFilter(
"(objectClass=jetspeed-2-role)"));
roleSearchConfig.setLdapIdAttribute("uid");
roleSearchConfig.setAttributeDefinitions(roleAttrDefs);
Modified: portals/jetspeed-2/portal/branches/security-refactoring/jetspeed-portal-resources/src/main/resources/assembly/security-ldap.xml
URL:
http://svn.apache.org/viewvc/portals/jetspeed-2/portal/branches/security-refactoring/jetspeed-portal-resources/src/main/resources/assembly/security-ldap.xml?rev=700523&r1=700522&r2=700523&view=diff==============================================================================
--- portals/jetspeed-2/portal/branches/security-refactoring/jetspeed-portal-resources/src/main/resources/assembly/security-ldap.xml (original)
+++ portals/jetspeed-2/portal/branches/security-refactoring/jetspeed-portal-resources/src/main/resources/assembly/security-ldap.xml Tue Sep 30 09:43:30 2008
@@ -98,10 +98,12 @@
</list>
</property>
</bean>
+
<bean id="UserDaoConfiguration" class="org.apache.jetspeed.security.mapping.ldap.dao.LDAPEntityDAOConfiguration">
<meta key="j2:cat" value="ldap" />
- <property name="baseDN" value="" />
- <property name="baseFilter">
+ <property name="baseDN" value="${ldap.base}" />
+ <property name="searchDN" value="" />
+ <property name="searchFilter">
<bean class="org.apache.jetspeed.security.mapping.ldap.filter.SimpleFilter">
<constructor-arg index="0" value="(objectClass=person)" />
</bean>
@@ -131,8 +133,9 @@
</bean>
<bean id="RoleDaoConfiguration" class="org.apache.jetspeed.security.mapping.ldap.dao.LDAPEntityDAOConfiguration">
<meta key="j2:cat" value="ldap" />
- <property name="baseDN" value="" />
- <property name="baseFilter">
+ <property name="baseDN" value="${ldap.base}" />
+ <property name="searchDN" value="${ldap.role.searchBase}" />
+ <property name="searchFilter">
<bean class="org.apache.jetspeed.security.mapping.ldap.filter.SimpleFilter">
<constructor-arg index="0" value="(objectClass=groupOfUniqueNames)" />
</bean>
@@ -167,8 +170,9 @@
</bean>
<bean id="GroupDaoConfiguration" class="org.apache.jetspeed.security.mapping.ldap.dao.LDAPEntityDAOConfiguration">
<meta key="j2:cat" value="ldap" />
- <property name="baseDN" value="ou=Groups,o=Jetspeed" />
- <property name="baseFilter">
+ <property name="baseDN" value="${ldap.base}" />
+ <property name="searchDN" value="${ldap.group.searchBase}" />
+ <property name="searchFilter">
<bean class="org.apache.jetspeed.security.mapping.ldap.filter.SimpleFilter">
<constructor-arg index="0" value="(objectClass=groupOfUniqueNames)" />
</bean>
Modified: portals/jetspeed-2/portal/branches/security-refactoring/jetspeed-portal-resources/src/main/resources/conf/jetspeed/jetspeed.properties
URL:
http://svn.apache.org/viewvc/portals/jetspeed-2/portal/branches/security-refactoring/jetspeed-portal-resources/src/main/resources/conf/jetspeed/jetspeed.properties?rev=700523&r1=700522&r2=700523&view=diff==============================================================================
--- portals/jetspeed-2/portal/branches/security-refactoring/jetspeed-portal-resources/src/main/resources/conf/jetspeed/jetspeed.properties (original)
+++ portals/jetspeed-2/portal/branches/security-refactoring/jetspeed-portal-resources/src/main/resources/conf/jetspeed/jetspeed.properties Tue Sep 30 09:43:30 2008
@@ -223,6 +223,9 @@
ldap.context.factory=com.sun.jndi.ldap.LdapCtxFactory
ldap.user.filter = (objectclass=person)
ldap.search.scope = 2
+ldap.role.searchBase=ou=Roles,o=Jetspeed
+ldap.group.searchBase=ou=Groups,o=Jetspeed
+
#-------------------------------------------------------------------------
# P R O F I L E R
#-------------------------------------------------------------------------
---------------------------------------------------------------------
To unsubscribe, e-mail:
jetspeed-dev-unsubscribe@...
For additional commands, e-mail:
jetspeed-dev-help@...