Hi,

We just have found a solution, so we share it. Below an example of a sync of some groups in LDAP from Oracle DB. We have a *..* association table named *assoc_group_user* between a *user* table and a *group* table.

In lsc.xml, a standard task :

    <task>
      <name>TaskStructuresGroups</name>
      <bean>org.lsc.beans.SimpleBean</bean>

      <databaseSourceService>
        <name>ora-source-service-groupe-structures</name>
        <connection reference="ora-source-conn" />
        <requestNameForList>*getGroupStructuresList*</requestNameForList>
        <requestNameForObject>*getGroupStructures*</requestNameForObject>
        
<requestNameForClean>*checkGroupStructuresForClean*</requestNameForClean>
      </databaseSourceService>

      <ldapDestinationService>
        <name>groupe-structures-dst-service</name>
        <connection reference="ldap-dst-conn" />
        <baseDn>ou=structures,ou=groups,dc=univ-jfc,dc=fr</baseDn>
        <pivotAttributes>
          <string>cn</string>
        </pivotAttributes>
        <fetchedAttributes>
          <string>cn</string>
          <string>description</string>
          <string>member</string>
          <string>objectClass</string>
        </fetchedAttributes>
        <getAllFilter><![CDATA[(objectClass=*)]]></getAllFilter>
        <getOneFilter><![CDATA[(&(objectClass=*)(cn={cn}))]]></getOneFilter>
      </ldapDestinationService>

      <propertiesBasedSyncOptions>
        <mainIdentifier>"cn=" + srcBean.getDatasetFirstValueById("cn") + 
",ou=structures,ou=groups,dc=univ-jfc,dc=fr"</mainIdentifier>
        <defaultDelimiter>;</defaultDelimiter>
        <defaultPolicy>FORCE</defaultPolicy>
        <conditions>
                <create>true</create>
                <update>true</update>
                <delete>false</delete>
                <changeId>false</changeId>
        </conditions>
      </propertiesBasedSyncOptions>
    </task>

In sql-map-config.xml, don't forget a reference to the iBatis mapping defined in the .d directory :

<sqlMapConfig>
 [...]
 <sqlMap url="file://${lsc.config}/*sql-map-config.d/group-structures.xml*"/>
</sqlMapConfig>

In sql-map-config.d/group-structures.xml, here custom resultMap, referencing an SQL view and a custom Java class

<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE sqlMap PUBLIC "-//iBATIS.com//DTD SQL Map 2.0//EN" 
"http://www.ibatis.com/dtd/sql-map-2.dtd";>

<sqlMap namespace="InetOrgPerson">
        <!-- columnIndices may are superfluous. But it is untested without -->
        <resultMap id="*mapGroupStructures*" class="java.util.HashMap">
                <result property="cn" column="cn" columnIndex="1" />
                <result property="description"  column="description" 
columnIndex="2" />
                <result property="member" column="member" 
columnIndex="3"*typeHandler="fr.jfc.misc.CLOBToStringTypeHandlerCallback"*  />
                <result property="objectClass"  column ="objectClass" 
columnIndex="4" />
        </resultMap>

        <select id="*getGroupStructures*" resultClass="java.util.HashMap" 
parameterClass="java.util.Map"*resultMap="mapGroupStructures"*>
                SELECT * FROM*DB.V_LDAP_GROUPS*
                WHERE "cn"=#cn#
        </select>

        <select id="*checkGroupStructuresForClean*" resultClass="java.util.HashMap" 
parameterClass="java.util.Map">
                SELECT "cn" FROM*DB.V_LDAP_GROUPS*
                WHERE "cn"=#cn#
        </select>

        <select id="*getGroupStructuresList*" resultClass="java.util.HashMap">
                SELECT "cn" FROM*DB.V_LDAP_GROUPS*
        </select>

</sqlMap>

In view V_LDAP_GROUPS (all *.sql applied to the database, in the reverse order)

CREATE OR REPLACE FORCE VIEW "DB"."V_LDAP_GROUPS" ("cn", "description", "member", 
"objectClass") AS
  SELECT
  g.id as "cn",
  g.desc as "description",
  *custom_string_agg*(CONCAT(CONCAT('uid=',u.login),',ou=people,dc=univ-jfc,dc=fr')) as 
"member",
  'groupOfURLs;supannEntite' as "objectClass"
FROM GROUPS g
LEFT JOIN ASSOC_GROUP_USERS a ON g.id = a.groupid
INNER JOIN USERS u ON a.userid = u.id
GROUP BY g.id, d.desc;

The custom function is defined by and returns a CLOB string (not a too short VARCHAR(4000) ) :

create or replace FUNCTION*custom_string_agg*  (p_input VARCHAR2)
RETURN*CLOB*
PARALLEL_ENABLE AGGREGATE USING*t_string_agg*;

The function uses a custom type definition :

create or replace TYPE*T_STRING_AGG*  AS OBJECT
(
  g_string  CLOB,

  STATIC FUNCTION ODCIAggregateInitialize(sctx  IN OUT  t_string_agg)
    RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateIterate(self   IN OUT  t_string_agg,
                                       value  IN      VARCHAR2 )
     RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,
                                         returnValue  OUT  CLOB,
                                         flags        IN   NUMBER)
    RETURN NUMBER,

  MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT  t_string_agg,
                                     ctx2  IN      t_string_agg)
    RETURN NUMBER
)


Then custom type declaration needs a custom body :

create or replace TYPE BODY*T_STRING_AGG*  AS

  STATIC FUNCTION ODCIAggregateInitialize(sctx  IN OUT  t_string_agg)
    RETURN NUMBER AS
  BEGIN
    sctx := t_string_agg(NULL);
    RETURN ODCIConst.Success;
  END ODCIAggregateInitialize;

  MEMBER FUNCTION ODCIAggregateIterate(self   IN OUT  t_string_agg,
                                       value  IN      VARCHAR2 )
     RETURN NUMBER AS
  BEGIN
    SELF.g_string := self.g_string || ';' || value;
    RETURN ODCIConst.Success;
  END ODCIAggregateIterate;

  MEMBER FUNCTION ODCIAggregateTerminate(self         IN   t_string_agg,
                                         returnValue  OUT  CLOB,
                                         flags        IN   NUMBER)
    RETURN NUMBER AS
  BEGIN
    returnValue := RTRIM(LTRIM(SELF.g_string, ';'), ';');
    RETURN ODCIConst.Success;
  END ODCIAggregateTerminate;

  MEMBER FUNCTION ODCIAggregateMerge(self  IN OUT  t_string_agg,
                                     ctx2  IN      t_string_agg)
    RETURN NUMBER AS
  BEGIN
    SELF.g_string := SELF.g_string || ';' || ctx2.g_string;
    RETURN ODCIConst.Success;
  END ODCIAggregateMerge;

END;

Then the piece a code is the custom Java class fr.jfc.misc.CLOBToStringTypeHandlerCallback

package fr.jfc.misc;

import java.sql.Clob;
import java.sql.SQLException;

import com.ibatis.sqlmap.client.extensions.ParameterSetter;
import com.ibatis.sqlmap.client.extensions.ResultGetter;
import com.ibatis.sqlmap.client.extensions.TypeHandlerCallback;

public class CLOBToStringTypeHandlerCallback implements TypeHandlerCallback {

        @Override
        public Object getResult(ResultGetter getter) throws SQLException {      
                
                Clob value = getter.getClob();
                String value2;
                if ( value.length() < Integer.MAX_VALUE ) {
                        value2 = value.getSubString(1, (int) value.length());
                } else {
                        value2 = "CLOB too long ( > Integer.MAX_VALUE )";
                }
                
                //throw new SQLException("getResult() ==> " + value2); // 
Stupid debug method :)
                return value2;
                
        }       

        @Override
        public void setParameter(ParameterSetter setter, Object parameter)
                        throws SQLException {
                throw new SQLException("unimplemented");
        }

        @Override
        public Object valueOf(String s) {
                throw new RuntimeException("unimplemented");
        }

}

You need this in the classpath to compile this code : /usr/lib/lsc/ibatis-sqlmap-2.3.4.726.jar Google find the ibatis-sqlmap-2.3.4.726-source.jar easily for you if you want to read the TypeHandlerCallback abstract class and attached javadoc.

You need to export this as JAR (Eclipse is your friend) and put it in the classpath of LSC, so could be in : /usr/lib/lsc/
For convinience, the JAR is attached, source included.

Then you are : you could have "member" concatenated strings up to 2 gigabytes. This will take stupid amount of ressources on the DB server, but it should be OK for reasonnable groups of persons in a university for example.

custom_agg_function is derived from https://asktom.oracle.com/pls/asktom/f?p=100:11:::::P11_QUESTION_ID:229614022562 no clue about licence, but seems at least free to share.

CLOBToStringTypeHandlerCallback, and XML config are from us and it is licenced under WTFPL <http://www.wtfpl.net/>.


We may try to make a documentation page about that.
@LSC guys : let us know how to capitalize efficiently this :)

Thanks all folks,
Ludovic Pouzenc

On 01/06/2015 17:42, Ludovic Pouzenc wrote:
Hi,

I try to use LSC to sync data from Oracle 10i DB to OpenLDAP 2.4.

I try to sync groups. I try to not use "automagic" management of member attributes by the LDAP itself because it causes stupid tragic problems with syncrepl replication system.

I manage to get a SQL query that returns entries like :

cn            objectClass               member                                  
                      ....

MyGroup1      groupOfNames;top          
uid=lpouzenc,ou=poeple,dc=univ-jfc,dc=fr;uid=otherguy,ou=poeple,dc=univ-jfc,dc=fr
I just put ojdbc6.jar in the lib path of LSC and it goes happily. I use Oracle Database 11g Release 2 (11.2.0.4) JDBC Drivers and associated javadoc.

In SQL code, I use a user func for concatenating strings because LIST_AGG() is Oracle >= 11 and WM_CONCAT() is unsupported, limited with VARCHAR2(4000) and enforces "," separator.

The "member" column out of my custom function use CLOB type. I have LSC errors on every insert or update. LDIF dumped in the logs are like :

juin 01 17:23:34 - ERROR - Error while adding entry 
cn=SG,ou=structures,ou=groups,dc=univ-jfc,dc=fr in directory 
:javax.naming.directory.InvalidAttributeValueException: Malformed 'member' 
attribute value; remaining name 'cn=SG,ou=structures,ou=groups'
juin 01 17:23:34 - ERROR - Error while synchronizing ID 
cn=SG,ou=structures,ou=groups,dc=univ-jfc,dc=fr: java.lang.Exception: Technical 
problem while applying modifications to the destination
# Mon Jun 01 17:23:34 CEST 2015
dn: cn=SG,ou=structures,ou=groups,dc=univ-jfc,dc=fr
changetype: add
member:*oracle.sql.CLOB@1f8db72d*
supannCodeEntite: 1494
cn: SG
description: SECRETARIAT GENERAL
objectClass: groupOfNames
objectClass: supannEntite
supannCodeEntiteParent: 2

It seems LSC just use toString() (or no conv at all) on this particular objdc type and this could not return a valid dn list.

I've tried some javascript, but I'm unsuccessful :
         <dataset>
           <name>member</name>
           <policy>FORCE</policy>
           <forceValues>
             
<string>js:srcBean.getDatasetFirstValueById("member").getSubString(1,10)</string>
           </forceValues>
         </dataset>

This complains that getSubString() method is not defined. This method is defined in oracle.sql.CLOB but not in generic JDBC types.

I know that, with the same lib in a java project that this is okay :
                CLOB field1 = new CLOB(null);
                field1.getSubString(1, 10);
but this is not : (Datum is a super type of CLOB. Any other more generic type do the job):
                Datum field2 = new CLOB(null);
                field2.getSubString(1, 10);
I'm afraid that I can't call specific oracle.sql.* methods from the Javascript code.

Here are my questions :

  * Could I use any CLOB with LSC ?
  * Is there an alternative to big strings concat's on multi-valued
    attributes when data came from RDBMS ?


Everything else is very great in LSC, many thanks to all folks that are involved.

Cheers,
--
Ludovic Pouzenc - Administrateur Système
CUFR J.-F. Champollion, Site d'Albi
Tél: 05.63.48.64.08, ext. 2221#
Unix is user-friendly; it's just picky about who its friends are.

--
Ludovic Pouzenc - Administrateur Système
CUFR J.-F. Champollion, Site d'Albi
Tél: 05.63.48.64.08, ext. 2221#
Unix is user-friendly; it's just picky about who its friends are.

Attachment: lsc-clob-adapter.jar
Description: application/java-archive

_______________________________________________________________
Ldap Synchronization Connector (LSC) - http://lsc-project.org

lsc-users mailing list
[email protected]
http://lists.lsc-project.org/listinfo/lsc-users

Reply via email to