Announcement Announcement Module
Collapse
No announcement yet.
Binding Multivalue Attributes Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Binding Multivalue Attributes

    My implementation using the LDAP module is working brilliantly for single-values attributes (thanks, guys). But when I added a List<String> private data member to my POJO and tried to bind that I got an UncategorizedLdapException caused by

    Code:
    org.springframework.ldap.UncategorizedLdapException: Operation failed; nested exception is javax.naming.directory.InvalidAttributeValueException: Malformed 'role' attribute value; remaining name 'consumerpersonid=bushgw, ou=people, ou=consumers, ou=hartfordlife, ou=external'
    Do I have to pass an Object like a String array instead of a List<String>? Please advise. Thanks. - %

  • #2
    Not a String []

    Just tried converting the value object from a List<String> to String [] - no success.

    %

    Comment


    • #3
      Looping Over List

      Wait - a smart co-worker might have spotted it.

      I need to loop over the collection and put that attribute name multiple times. I'm writing a JUnit test now to try it out - will report back.

      %

      Comment


      • #4
        Only Last Attribute Makes It Into Directory

        When I loop over a List<String> and try to insert a multi-valued attribute, only the last value appears.


        %

        Comment


        • #5
          It's hard to say without any code samples. Usually we need to see your configuration and the piece of code that performs the actual operation. Do you use a DirContextAdapter and the setAttributeValues method?

          This example (from the integration tests) shows how to bind using DirContextAdapter:

          Code:
              public void testBindAndUnbind_DirContextAdapter_Plain() {
                  DirContextAdapter adapter = new DirContextAdapter();
                  adapter.setAttributeValues("objectclass", new String[] { "top",
                          "person" });
                  adapter.setAttributeValue("cn", "Some Person4");
                  adapter.setAttributeValue("sn", "Person4");
          
                  tested.bind(DN, adapter, null);
                  verifyBoundCorrectData();
                  tested.unbind(DN);
                  verifyCleanup();
              }
          Note the 's' on setAttributeValues for multi-value attributes.

          Comment


          • #6
            Hi Ulrik,

            Sorry for the lack of code and config. I'll post it below.

            I'm not using DirContextAdapter, just an AttributesMapper class. Sounds like I've missed something important here. - %

            Code:
            import com.hartford.census.model.Consumer;
            import org.springframework.ldap.support.filter.AndFilter;
            import org.springframework.ldap.support.filter.EqualsFilter;
            import org.springframework.ldap.support.filter.Filter;
            
            import javax.naming.Name;
            import javax.naming.NamingException;
            import javax.naming.directory.Attributes;
            import javax.naming.directory.BasicAttribute;
            import javax.naming.directory.BasicAttributes;
            import java.util.Iterator;
            import java.util.List;
            
            /**
             * Class for mapping LDAP attributes to a Consumer object. This class centralizes all LDAP schema details for a Consumer
             * in one place.
             * @since Nov 30, 2006 Time: 3:49:56 PM
             */
            public class ConsumerAttributesMapper extends AbstractAttributesMapper
            {
               /**
                * Map Attributes to an object. The supplied attributes are the attributes from a single SearchResult.
                * @param attributes attributes from a SearchResult.
                * @return an object built from the attributes.
                * @throws NamingException if any error occurs mapping the attributes
                */
               public Object mapFromAttributes(Attributes attributes) throws NamingException
               {
                  Long id = null;
                  String firstName = getAttribute("givenName", attributes).get(0);
                  String lastName = getAttribute("sn", attributes).get(0);
                  String username = getAttribute("higconsumerpersonID", attributes).get(0);
                  String password = getAttribute("userPassword", attributes).get(0);
                  List<String> applications = getAttribute("higapplicationindicator", attributes);
                  List<String> roles = getAttribute("higgbdenrolrole", attributes);
            
                  return new Consumer(id, firstName, lastName, username, password, applications, roles);
               }
            
               /**
                * Map a Consumer instance into naming Attributes
                * @param consumer Consumer instance to map
                * @return naming Attributes for the given Consumer
                */
               public Attributes getAttributes(Consumer consumer)
               {
                  boolean ignoreCase = true;
                  Attributes attributes = new BasicAttributes(ignoreCase);
            
                  // Add in the required attributes first
                  BasicAttribute objectclass = new BasicAttribute("objectclass");
                  for (Iterator iterator = getObjectClasses().iterator(); iterator.hasNext();)
                  {
                     objectclass.add((String) iterator.next());
                  }
                  attributes.put(objectclass);
            
                  attributes.put("cn", consumer.getUsername());
            
                  // Now add in the attributes for this Consumer
                  attributes.put("sn", consumer.getLastName());
                  attributes.put("givenName", consumer.getFirstName());
                  attributes.put("higconsumerpersonid", consumer.getUsername());
                  StringBuffer sb = new StringBuffer(512);
                  for (String role : consumer.getRoles())
                  {
                     sb.append(role).append(" ");
                  }
                  attributes.put("higgbdenrolrole", sb.toString());
                  attributes.put("uid", consumer.getUsername());
                  attributes.put("userpassword", consumer.getPassword());
                  sb = new StringBuffer(512);
                  for (String application : consumer.getApplications())
                  {
                     sb.append(application).append(" ");
                  }
            
                  attributes.put("higapplicationindicator", sb.toString());
                  return attributes;
               }
            
               /**
                * Helper method for creating a Filter for full name
                * @param firstName
                * @param lastName
                * @return AndFilter combining firstName and lastName criteria
                */
               public Filter createNameFilter(String firstName, String lastName)
               {
                  AndFilter filter = new AndFilter();
            
                  filter.and(new EqualsFilter("givenName", firstName));
                  filter.and(new EqualsFilter("sn", lastName));
            
                  return filter;
               }
            
               /**
                * Helper method for creating a Filter for last name
                * @param lastName
                * @return Filter by last name
                */
               public Filter createLastNameFilter(String lastName)
               {
                  return new EqualsFilter("sn", lastName);
               }
            
               /**
                * Helper method for building an LDAP distinguished name
                * @param consumer
                * @return relative distinguished name for the given Employee
                */
               public Name buildDn(Consumer consumer)
               {
                  return buildDn(consumer.getUsername().toString());
               }
            
               /**
                * Helper method for building an LDAP distinguished name
                * @param username to create DN relative to tree root
                * @return relative distinguished name
                */
               public Name buildDn(String username)
               {
                  return buildDn("higconsumerpersonID", username);
               }
            }

            Comment


            • #7
              Read The Docs

              Hi Ulrik,

              Now I see what happened. When I open the reference docs, I see that DirContextAdapter shows up in section 3. I thought that what I saw in sections 1 and 2 was sufficient for my needs, so I didn't read further. Wrong.

              I'll keep reading today and let you know when I figure out just how right you are. Thanks again for your help and patience. It would be easy to dismiss this thread with "Read The Fine Manual".

              The LDAP module is a great piece of work. Kudos to the LDAP team. Sincerely, %

              Comment


              • #8
                Many thanks for your kind words.

                It is of course possible to use an AttributesMapper even for complex tasks. However, when the amount of attribute shuffling increases, the complexity of the Attributes concept becomes painfully obvious. DirContextAdapter makes it a lot easier. Plus the benefit you get in that it keeps track of your changes, so that you can simply ask it: "get the modifications since I started changing you". Very handy in an update scenario.

                Comment


                • #9
                  Code:
                  public class ConsumerAttributesMapper extends AbstractAttributesMapper
                  Even if you decide to switch to ContextMapper, I think we should find out what the problem is with your original code. In order to do that, I need to look at your AbstractAttributesMapper as well.

                  Comment


                  • #10
                    You handle the objectClass multi-value attribute correctly:

                    Code:
                    BasicAttribute objectclass = new BasicAttribute("objectclass");
                    for (Iterator iterator = getObjectClasses().iterator(); iterator.hasNext();)
                    {
                       objectclass.add((String) iterator.next());
                    }
                    attributes.put(objectclass);
                    What confuses me is that you treat the other multi-value attributes in a different way:

                    Code:
                    StringBuffer sb = new StringBuffer(512);
                    for (String role : consumer.getRoles())
                    {
                       sb.append(role).append(" ");
                    }
                    attributes.put("higgbdenrolrole", sb.toString());
                    I would say that this is your problem. If you treat these attributes just as objectClass, it would likely produce better results. I assume that "higgbdenrolrole" and "higapplicationindicator" are multi-value attributes in your schema.

                    Comment


                    • #11
                      Thrashing

                      Unfortunately, the code I posted might have been in "mid-thrash" as I tried things to fix it.

                      I've rewritten everything to use the DirContextAdapter. Still working fine for single-valued, failing for multi-valued. I'm getting an LDAP 65 error code when I try to create a Consumer with multi-valued application and role.

                      Any way to get which attribute is the offending one from the exception? It's great to know that I'm missing a required attribute or doing something else heinous, but it'd be better to know which attribute was causing the problem.

                      %

                      Comment


                      • #12
                        Send the stacktrace, the code and the config, and we'll take a look.

                        Comment


                        • #13
                          Tried to send the code, but it exceeded the 10K char limit. What artifacts will help most? I'll start with the stack trace:

                          Code:
                          org.springframework.ldap.UncategorizedLdapException: Operation failed; nested exception is javax.naming.directory.SchemaViolationException: [LDAP: error code 65 - Object Class Violation]; remaining name 'higconsumerpersonid=bushgw, ou=people, ou=consumers, ou=hartfordlife, ou=external'
                          Caused by: javax.naming.directory.SchemaViolationException: [LDAP: error code 65 - Object Class Violation]; remaining name 'higconsumerpersonid=bushgw, ou=people, ou=consumers, ou=hartfordlife, ou=external'
                          	at com.sun.jndi.ldap.LdapCtx.mapErrorCode(LdapCtx.java:3016)
                          	at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2931)
                          	at com.sun.jndi.ldap.LdapCtx.processReturnCode(LdapCtx.java:2737)
                          	at com.sun.jndi.ldap.LdapCtx.c_bind(LdapCtx.java:379)
                          	at com.sun.jndi.toolkit.ctx.ComponentDirContext.p_bind(ComponentDirContext.java:277)
                          	at com.sun.jndi.toolkit.ctx.PartialCompositeDirContext.bind(PartialCompositeDirContext.java:197)
                          	at javax.naming.directory.InitialDirContext.bind(InitialDirContext.java:163)
                          	at org.springframework.ldap.LdapTemplate$15.executeWithContext(LdapTemplate.java:773)
                          	at org.springframework.ldap.LdapTemplate.executeWithContext(LdapTemplate.java:641)
                          	at org.springframework.ldap.LdapTemplate.executeReadWrite(LdapTemplate.java:636)
                          	at org.springframework.ldap.LdapTemplate.bind(LdapTemplate.java:770)
                          	at com.hartford.census.dao.ldap.ConsumerDaoImpl.create(ConsumerDaoImpl.java:102)
                          	at com.hartford.census.dao.ConsumerDaoTest.testClearApplicationAttribute(ConsumerDaoTest.java:105)
                          	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                          	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                          	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                          	at org.springframework.test.ConditionalTestCase.runBare(ConditionalTestCase.java:69)
                          	at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:40)
                          	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                          	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
                          	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
                          	at com.intellij.rt.execution.application.AppMain.main(AppMain.java:90)
                          And maybe the JUnit test. Two out of the three are green; only the last test is red. The last test fails when I try to create a Consumer object with multi-valued application indicators and roles. Create works fine if I only have single-valued lists for both. This suggests that the required elements are in place.

                          Code:
                          package com.hartford.census.dao;
                          
                          import com.hartford.census.model.Consumer;
                          import org.springframework.test.AbstractDependencyInjectionSpringContextTests;
                          import org.springframework.ldap.EntryNotFoundException;
                          
                          import java.util.List;
                          import java.util.Arrays;
                          
                          /**
                           * JUnit test class for ConsumerDao
                           * @author md87020
                           * @since Dec 13, 2006 Time: 2:27:03 PM
                           */
                          public class ConsumerDaoTest extends AbstractDependencyInjectionSpringContextTests
                          {
                             private ConsumerDao consumerDao;
                          
                          
                             public void setConsumerDao(ConsumerDao consumerDao)
                             {
                                this.consumerDao = consumerDao;
                             }
                          
                             protected String[] getConfigLocations()
                             {
                                return new String[]
                                {
                                "classpath:applicationContext-ldap.xml",
                                };
                             }
                          
                             public void testFindByUsernameSuccess()
                             {
                                Long id = null;
                                String firstName = "givenname";
                                String lastName = "Surname";
                                String username = "1eetest";
                                String password = "";
                          
                                Consumer expected = new Consumer(id, firstName, lastName, username, password);
                                Consumer actual = consumerDao.findByUsername(username);
                                assertNotNull("consumer with username " + username + " should not be null", actual);
                                assertEquals("could not find consumer with username " + username, expected, actual);
                             }
                          
                             public void testCreateAndDelete()
                             {
                                Long id = null;
                                String firstName = "George";
                                String lastName = "Bush";
                                String username = "bushgw";
                                String password = "prez";
                          
                                // First delete that Consumer if they appear
                                try
                                {
                                   Consumer alreadyExists = consumerDao.findByUsername(username);
                                   consumerDao.delete(alreadyExists);
                                }
                                catch (EntryNotFoundException e)
                                {
                                   System.out.println("check to see if the exception is caught");
                                   // Just continue if the entry doesn't appear         
                                }
                          
                                // Create a new Consumer and insert it into the directory
                                Consumer consumer = new Consumer(id, firstName, lastName, username, password);
                                consumerDao.create(consumer);
                          
                                // Check to make sure the creation succeeded
                                Consumer actual = consumerDao.findByUsername(username);
                                assertNotNull("Could not find Consumer with username " + username, actual);
                                assertEquals("New consumer was not created", consumer, actual);
                                assertEquals("wrong applications", consumer.getApplications(), actual.getApplications());
                                assertEquals("wrong roles", consumer.getRoles(), actual.getRoles());
                          
                                // Remove the new Consumer
                                consumerDao.delete(consumer);
                          
                                // Make sure the delete succeeded
                                try
                                {
                                   actual = consumerDao.findByUsername(username);
                                   fail("Consumer with username " + username + " was not deleted");
                                }
                                catch (EntryNotFoundException e)
                                {
                                   return;
                                }
                             }
                          
                             public void testClearApplicationAttribute()
                             {
                                Long id = null;
                                String firstName = "George";
                                String lastName = "Bush";
                                String username = "bushgw";
                                String password = "prez";
                                List<String> applications = Arrays.asList(new String [] { "foo", "bar", "baz", "bat", });
                                List<String> roles = Arrays.asList(new String [] { "foo", "bar", "baz", });
                          
                                // Create a new Consumer and insert it into the directory
                                Consumer consumer = new Consumer(id, firstName, lastName, username, password, applications, roles);
                                consumerDao.create(consumer);
                          
                                // Check to make sure the creation succeeded
                                Consumer actual = consumerDao.findByUsername(username);
                                assertNotNull("Could not find Consumer with username " + username, actual);
                                assertEquals("New consumer was not created", consumer, actual);
                                assertEquals("applications did not make it in", applications, actual.getApplications());
                                assertEquals("roles did not make it in", roles, actual.getRoles());
                          
                                // Remove the new Consumer
                                consumerDao.delete(consumer);
                          
                                // Make sure the delete succeeded
                                try
                                {
                                   actual = consumerDao.findByUsername(username);
                                   fail("Consumer with username " + username + " was not deleted");
                                }
                                catch (Exception e)
                                {
                                   return;
                                }
                             }
                          }
                          Here's a dumb question: How can I tell if the LDAP attribute is multi-valued or single-valued in the schema? I'm using JXplorer as my LDAP client.

                          %


                          %

                          Comment


                          • #14
                            One last bit: here's the app context config:

                            Code:
                            <?xml version="1.0" encoding="UTF-8"?>
                            
                            <!--
                              - Application context definition for JPetStore's business layer.
                              - Contains bean references to the transaction manager and to the DAOs in
                              - dataAccessContext-local/jta.xml (see web.xml's "contextConfigLocation").
                              -->
                            <beans xmlns="http://www.springframework.org/schema/beans"
                                   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                                   xmlns:aop="http://www.springframework.org/schema/aop"
                                   xmlns:tx="http://www.springframework.org/schema/tx"
                                   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                                   http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
                                   http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd"
                                   default-autowire="no">
                            
                                <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
                                    <property name="location">
                                        <value>classpath:directoryaccess.properties</value>
                                    </property>
                                </bean>
                            
                                <bean id="contextSource" class="org.springframework.ldap.support.LdapContextSource">
                                    <property name="url" value="ldap://${ldapHost}:${ldapPort}"/>
                                    <property name="base" value="${searchBase}"/>
                                    <property name="userName" value="${loginDN}"/>
                                    <property name="password" value="${password}"/>
                                </bean>
                            
                                <bean id="ldapTemplate" class="org.springframework.ldap.LdapTemplate">
                                    <constructor-arg ref="contextSource"/>
                                </bean>
                            
                                <bean id="employeeDao" class="com.hartford.census.dao.ldap.EmployeeDaoImpl">
                                    <constructor-arg><ref bean="ldapTemplate"/></constructor-arg>
                                    <constructor-arg><ref bean="employeeAttributesMapper"/></constructor-arg>
                                </bean>
                            
                                <bean id="consumerDao" class="com.hartford.census.dao.ldap.ConsumerDaoImpl">
                                    <constructor-arg><ref bean="ldapTemplate"/></constructor-arg>
                                    <constructor-arg><ref bean="consumerContextMapper"/></constructor-arg>
                                </bean>
                            
                                <bean id="employeeAttributesMapper" class="com.hartford.census.dao.ldap.EmployeeAttributesMapper">
                                    <property name="baseDn">
                                        <value>dc=hig,dc=com</value>
                                    </property>
                                    <property name="organizationalUnits">
                                        <list>
                                            <value>internal</value>
                                            <value>onlineenrollment</value>
                                        </list>
                                    </property>
                                    <property name="objectClasses">
                                        <list>
                                            <value>top</value>
                                            <value>person</value>
                                            <value>higorgperson</value>
                                            <value>inetOrgPerson</value>
                                            <value>organizationalPerson</value>
                                        </list>
                                    </property>
                                </bean>
                            
                                <bean id="consumerAttributesMapper" class="com.hartford.census.dao.ldap.ConsumerAttributesMapper">
                                    <property name="baseDn">
                                        <value>dc=hig,dc=com</value>
                                    </property>
                                    <property name="organizationalUnits">
                                        <list>
                                            <value>external</value>
                                            <value>hartfordlife</value>
                                            <value>consumers</value>
                                            <value>people</value>
                                        </list>
                                    </property>
                                    <property name="objectClasses">
                                        <list>
                                            <value>top</value>
                                            <value>person</value>
                                            <value>HigConsumer</value>
                                            <value>inetOrgPerson</value>
                                            <value>organizationalPerson</value>
                                        </list>
                                    </property>
                                </bean>
                            
                               <bean id="consumerContextMapper" class="com.hartford.census.dao.ldap.ConsumerContextMapper">
                                  <constructor-arg><ref bean="consumerAttributesMapper"/></constructor-arg>
                               </bean>
                            
                               <bean id="testDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
                                  <property name="driverClassName">
                                     <value>oracle.jdbc.driver.OracleDriver</value>
                                  </property>
                                  <property name="url">
                                     <value>jdbc:oracle:thin:@gbdsns01:1521:gbdss01g</value>
                                  </property>
                                  <property name="username">
                                     <value>enrolldev</value>
                                  </property>
                                  <property name="password">
                                     <value>venus10</value>
                                  </property>
                               </bean>
                            
                               <bean id="caseDao" class="com.hartford.census.dao.jdbc.CaseDaoImpl">
                                  <property name="dataSource">
                                     <ref local="testDataSource"/>
                                  </property>
                               </bean>
                            <!--
                               <bean id="caseDao" class="com.hartford.census.dao.hibernate.CaseDaoImpl">
                                  <constructor-arg>
                                     <ref local="sessionFactory"/>
                                  </constructor-arg>
                               </bean>
                            -->
                            
                               <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                                  <property name="dataSource">
                                     <ref bean="testDataSource"/>
                                  </property>
                               </bean>
                            
                               <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
                                  <property name="dataSource"><ref local="testDataSource"/></property>
                                  <property name="mappingResources">
                                     <list>
                                        <value>com/hartford/census/model/Case.hbm.xml</value>
                                        <value>com/hartford/census/model/Consumer.hbm.xml</value>
                                        <value>com/hartford/census/model/Client.hbm.xml</value>
                                     </list>
                                  </property>
                                  <property name="hibernateProperties">
                                     <props>
                                        <prop key="hibernate.dialect">org.hibernate.dialect.Oracle9Dialect</prop>
                                        <prop key="hibernate.show_sql">true</prop>
                                        <prop key="hibernate.generate_statistics">true</prop>
                                        <prop key="hibernate.cache.provider_class">org.hibernate.cache.EhCacheProvider</prop>
                                        <prop key="hibernate.cache.use_query_cache">true</prop>
                                        <prop key="hibernate.query.factory_class">org.hibernate.hql.classic.ClassicQueryTranslatorFactory</prop>
                                     </props>
                                  </property>
                               </bean>
                            
                            </beans>
                            %

                            Comment


                            • #15
                              I'll need the code that performs the mapping as well, ie your ContextMapper implementation.

                              We'll probably need to look at your schema as well. What server are you using?

                              Comment

                              Working...
                              X