001/*
002 *  Copyright 2016 Anyware Services
003 *
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *
008 *      http://www.apache.org/licenses/LICENSE-2.0
009 *
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package org.ametys.plugins.contentio.synchronize.impl;
017
018import java.util.Arrays;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.commons.lang.StringUtils;
030import org.slf4j.Logger;
031
032import org.ametys.core.user.directory.UserDirectory;
033import org.ametys.core.user.population.UserPopulation;
034import org.ametys.core.user.population.UserPopulationDAO;
035import org.ametys.core.util.JSONUtils;
036import org.ametys.plugins.contentio.synchronize.AbstractSynchronizableContentsCollection;
037import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollection;
038import org.ametys.plugins.core.impl.user.directory.JdbcUserDirectory;
039import org.ametys.plugins.core.impl.user.directory.LdapUserDirectory;
040
041/**
042 * Implementation of {@link SynchronizableContentsCollection} to be synchronized with a {@link UserPopulation} of the CMS.
043 */
044public class UserPopulationSynchronizableContentsCollection extends AbstractSynchronizableContentsCollection
045{
046    /** Name of parameter holding the id of population */
047    protected static final String __PARAM_POPULATION_ID = "populationId";
048    /** Name of parameter for the login metadata */
049    protected static final String __PARAM_LOGIN_METADATA_NAME = "login";
050    /** Name of parameter for the firstname metadata */
051    protected static final String __PARAM_FIRSTNAME_METADATA_NAME = "firstname";
052    /** Name of parameter for the lastname metadata */
053    protected static final String __PARAM_LASTNAME_METADATA_NAME = "lastname";
054    /** Name of parameter for the email metadata */
055    protected static final String __PARAM_EMAIL_METADATA_NAME = "email";
056    /** Name of parameter holding the fields mapping */
057    protected static final String __PARAM_MAPPING = "mapping";
058    /** Name of parameter into mapping holding the synchronized property */
059    protected static final String __PARAM_MAPPING_SYNCHRO = "synchro";
060    /** Name of parameter into mapping holding the path of metadata */
061    protected static final String __PARAM_MAPPING_METADATA_REF = "metadata-ref";
062    /** Name of parameter into mapping holding the remote attribute */
063    protected static final String __PARAM_MAPPING_ATTRIBUTE_PREFIX = "attribute-";
064    
065    private static final int __LDAP_DEFAULT_PAGE_SIZE = 1000;
066    
067    /** The DAO for user populations */
068    protected UserPopulationDAO _userPopulationDAO;
069    /** The service manager */
070    protected ServiceManager _manager;
071    /** The JSON utils */
072    protected JSONUtils _jsonUtils;
073    
074    @Override
075    public void service(ServiceManager manager) throws ServiceException
076    {
077        super.service(manager);
078        _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE);
079        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
080        _manager = manager;
081    }
082    
083    @Override
084    protected void _internalPopulate(Logger logger)
085    {
086        UserPopulation population = _userPopulationDAO.getUserPopulation(getPopulationId());
087        
088        for (UserDirectory userDirectory : population.getUserDirectories())
089        {
090            if (userDirectory instanceof LdapUserDirectory)
091            {
092                importContentFromLdap((LdapUserDirectory) userDirectory, logger);
093            }
094            else if (userDirectory instanceof JdbcUserDirectory)
095            {
096                // TODO handle SQL case
097//                _importFromSql((JdbcUserDirectory) userDirectory, logger);
098            }
099        }
100    }
101    
102    /**
103     * Populate contents from a LDAP user directory of the population
104     * @param userDirectory The LDAP user directory
105     * @param logger The logger
106     */
107    protected void importContentFromLdap(LdapUserDirectory userDirectory, Logger logger)
108    {
109        Map<String, Object> ldapParameterValues = userDirectory.getParameterValues();
110        String dataSourceId = (String) ldapParameterValues.get(LdapUserDirectory.PARAM_DATASOURCE_ID);
111        String relativeDN = (String) ldapParameterValues.get(LdapUserDirectory.PARAM_USERS_RELATIVE_DN);
112        String filter = (String) ldapParameterValues.get(LdapUserDirectory.PARAM_USERS_OBJECT_FILTER);
113        String searchScope = (String) ldapParameterValues.get(LdapUserDirectory.PARAM_USERS_SEARCH_SCOPE);
114        String loginAttr = (String) ldapParameterValues.get(LdapUserDirectory.PARAM_USERS_LOGIN_ATTRIBUTE);
115        String firstNameAttr = (String) ldapParameterValues.get(LdapUserDirectory.PARAM_USERS_FIRSTNAME_ATTRIBUTE);
116        String lastNameAttr = (String) ldapParameterValues.get(LdapUserDirectory.PARAM_USERS_LASTNAME_ATTRIBUTE);
117        String emailAttr = (String) ldapParameterValues.get(LdapUserDirectory.PARAM_USERS_EMAIL_ATTRIBUTE);
118        
119        Map<String, List<String>> udMapping = getMapping().get(userDirectory.getId());
120        if (udMapping == null)
121        {
122            udMapping = new HashMap<>();
123        }
124        udMapping.put(getLoginMetadata(), Collections.singletonList(loginAttr));
125        udMapping.put(getFirstNameMetadata(), Collections.singletonList(firstNameAttr));
126        udMapping.put(getLastNameMetadata(), Collections.singletonList(lastNameAttr));
127        udMapping.put(getEmailMetadata(), Collections.singletonList(emailAttr));
128        
129        try
130        {
131            LDAPCollectionHelper ldapHelper = (LDAPCollectionHelper) _manager.lookup(LDAPCollectionHelper.ROLE);
132            ldapHelper._delayedInitialize(dataSourceId);
133            
134            Map<String, Map<String, List<Object>>> remoteValuesByContent = ldapHelper.search(getId(), __LDAP_DEFAULT_PAGE_SIZE, relativeDN, filter, searchScope, udMapping, loginAttr, logger);
135            _nbError = ldapHelper.getNbErrors();
136            _hasGlobalError = ldapHelper.hasGlobalError();
137            
138            for (String idValue : remoteValuesByContent.keySet())
139            {
140                Map<String, List<Object>> remoteValues = remoteValuesByContent.get(idValue);
141                importContent(idValue, remoteValues, logger);
142            }
143        }
144        catch (Exception e)
145        {
146            logger.error("An error occured when importing from LDAP UserDirectory", e);
147        }
148    }
149    
150    /**
151     * Get the id of the user population
152     * @return The id of user population
153     */
154    public String getPopulationId()
155    {
156        return (String) _modelParamValues.get(__PARAM_POPULATION_ID);
157    }
158    
159    @Override
160    public String getIdField()
161    {
162        return getLoginMetadata();
163    }
164    
165    /**
166     * Get the metadata name for the login
167     * @return The the metadata name for the login
168     */
169    public String getLoginMetadata()
170    {
171        return (String) _modelParamValues.get(__PARAM_LOGIN_METADATA_NAME);
172    }
173    
174    /**
175     * Get the metadata name for the first name
176     * @return The the metadata name for the first name
177     */
178    public String getFirstNameMetadata()
179    {
180        return (String) _modelParamValues.get(__PARAM_FIRSTNAME_METADATA_NAME);
181    }
182    
183    /**
184     * Get the metadata name for the last name
185     * @return The the metadata name for the last name
186     */
187    public String getLastNameMetadata()
188    {
189        return (String) _modelParamValues.get(__PARAM_LASTNAME_METADATA_NAME);
190    }
191    
192    /**
193     * Get the metadata name for the email
194     * @return The the metadata name for the email
195     */
196    public String getEmailMetadata()
197    {
198        return (String) _modelParamValues.get(__PARAM_EMAIL_METADATA_NAME);
199    }
200    
201    @Override
202    public Set<String> getExternalOnlyFields()
203    {
204        Set<String> extFields = new HashSet<>();
205        
206        String mappingAsString = (String) getParameterValues().get(__PARAM_MAPPING);
207        if (StringUtils.isNotEmpty(mappingAsString))
208        {
209            List<Object> mappingAsList = _jsonUtils.convertJsonToList(mappingAsString);
210            for (Object object : mappingAsList)
211            {
212                @SuppressWarnings("unchecked")
213                Map<String, Object> field = (Map<String, Object>) object;
214                boolean isSynchronized = field.containsKey(__PARAM_MAPPING_SYNCHRO) ? (Boolean) field.get(__PARAM_MAPPING_SYNCHRO) : false;
215                if (!isSynchronized)
216                {
217                    extFields.add((String) field.get(__PARAM_MAPPING_METADATA_REF));
218                }
219            }
220        }
221        
222        return extFields;
223    }
224    
225    @Override
226    public Set<String> getLocalAndExternalFields()
227    {
228        Set<String> syncFields = new HashSet<>();
229        
230        String mappingAsString = (String) getParameterValues().get(__PARAM_MAPPING);
231        if (StringUtils.isNotEmpty(mappingAsString))
232        {
233            List<Object> mappingAsList = _jsonUtils.convertJsonToList(mappingAsString);
234            for (Object object : mappingAsList)
235            {
236                @SuppressWarnings("unchecked")
237                Map<String, Object> field = (Map<String, Object>) object;
238                boolean isSynchronized = field.containsKey(__PARAM_MAPPING_SYNCHRO) ? (Boolean) field.get(__PARAM_MAPPING_SYNCHRO) : false;
239                if (isSynchronized)
240                {
241                    syncFields.add((String) field.get(__PARAM_MAPPING_METADATA_REF));
242                }
243            }
244        }
245        
246        return syncFields;
247    }
248    
249    /**
250     * Get the field mapping
251     * @return The mapping
252     */
253    public Map<String, Map<String, List<String>>> getMapping()
254    {
255        Map<String, Map<String, List<String>>> mapping = new HashMap<>();
256        
257        String mappingAsString = (String) getParameterValues().get(__PARAM_MAPPING);
258        if (StringUtils.isNotEmpty(mappingAsString))
259        {
260            List<Object> list = _jsonUtils.convertJsonToList(mappingAsString);
261            for (Object obj : list)
262            {
263                @SuppressWarnings("unchecked")
264                Map<String, Object> field = (Map<String, Object>) obj;
265                String metadataRef = (String) field.get(__PARAM_MAPPING_METADATA_REF);
266                
267                String prefix = __PARAM_MAPPING_ATTRIBUTE_PREFIX;
268                for (String prefixedUserDirectoryKey : _getUserDirectoryKeys(field, prefix))
269                {
270                    String userDirectoryKey = prefixedUserDirectoryKey.substring(prefix.length());
271                    if (!mapping.containsKey(userDirectoryKey))
272                    {
273                        mapping.put(userDirectoryKey, new HashMap<>());
274                    }
275                    
276                    String[] attributes = ((String) field.get(prefixedUserDirectoryKey)).split(",");
277                    
278                    Map<String, List<String>> userDirectoryMapping = mapping.get(userDirectoryKey);
279                    userDirectoryMapping.put(metadataRef, Arrays.asList(attributes));
280                }
281            }
282        }
283        
284        return mapping;
285    }
286    
287    private Set<String> _getUserDirectoryKeys(Map<String, Object> field, String prefix)
288    {
289        return field.keySet().stream()
290                .filter(name -> name.startsWith(prefix))
291                .collect(Collectors.toSet());
292    }
293}