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.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022
023import org.apache.avalon.framework.component.Component;
024import org.apache.avalon.framework.service.ServiceException;
025import org.apache.avalon.framework.service.ServiceManager;
026import org.apache.commons.lang3.StringUtils;
027import org.slf4j.Logger;
028
029import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollection;
030import org.ametys.plugins.contentio.synchronize.impl.LDAPCollectionHelper.LDAPCollectionHelperSearchResult;
031import org.ametys.runtime.i18n.I18nizableText;
032
033/**
034 * Implementation of {@link SynchronizableContentsCollection} to be synchronized with a LDAP data source
035 */
036public class LDAPSynchronizableContentsCollection extends AbstractDataSourceSynchronizableContentsCollection implements Component
037{
038    private static final String __PARAM_LDAP_SCOPE = "scope";
039    private static final String __PARAM_LDAP_RELATIVE_DN = "peopleDN";
040    private static final String __PARAM_LDAP_FILTER = "baseFilter";
041    
042    /** The helper for LDAP connection */
043    protected LDAPCollectionHelper _ldapHelper;
044    
045    @Override
046    public void service(ServiceManager serviceManager) throws ServiceException
047    {
048        super.service(serviceManager);
049        _ldapHelper = (LDAPCollectionHelper) serviceManager.lookup(LDAPCollectionHelper.ROLE);
050    }
051        
052    @Override
053    protected Map<String, Map<String, Object>> internalSearch(Map<String, Object> searchParameters, int offset, int limit, List<Object> sort, Logger logger)
054    {
055        // Sort is ignored in LDAP request
056        
057        Map<String, List<String>> mapping = getMapping();
058        Map<String, Map<String, Object>> searchResults = new HashMap<>();
059        
060        String idField = getIdField();
061        List<String> remoteKeys = mapping.get(idField);
062        if (remoteKeys != null && remoteKeys.size() > 0)
063        {
064            // Filter from search parameters
065            String filter = getFilter();
066            List<String> filters = new ArrayList<>();
067            if (StringUtils.isNotEmpty(filter))
068            {
069                filters.add(filter);
070            }
071            if (searchParameters != null)
072            {
073                for (String parameterName : searchParameters.keySet())
074                {
075                    filters.add(parameterName + "=" + searchParameters.get(parameterName));
076                }
077            }
078            String filtersReduced = filters.stream().filter(StringUtils::isNotEmpty).map(s -> "(" + s + ")").reduce("", (s1, s2) -> s1 + s2);
079            if (!filtersReduced.isEmpty())
080            {
081                filtersReduced = "(&" + filtersReduced + ")";
082            }
083
084            try
085            {
086                String remoteKey = remoteKeys.get(0);
087                LDAPCollectionHelperSearchResult results = _ldapHelper.search(getId(), getRelativeDN(), filtersReduced, getSearchScope(), offset, limit, mapping, remoteKey, logger, getDataSourceId());
088    
089                searchResults = new HashMap<>();
090                for (Map<String, Object> result : results.searchResults().values())
091                {
092                    Object sccValues = result.get(remoteKey);
093                    String transformedId = checkAndTransformIdObjectValue(remoteKey, sccValues, logger);
094                    if (transformedId != null)
095                    {
096                        result.put(SCC_UNIQUE_ID, transformedId);
097                        result.put(remoteKey, List.of(transformedId));
098                        searchResults.put(transformedId, result);
099                    }
100                }
101                
102                _nbError = results.nbErrors();
103                _hasGlobalError = results.hasGlobalError();
104            }
105            catch (Exception e)
106            {
107                throw new RuntimeException("An error occured when importing from LDAP UserDirectory", e);
108            }
109        }
110        else
111        {
112            _nbError = 1;
113            logger.error("Missing LDAP attribute in mapping for field '{}' holding the unique identifier", idField);
114        }
115        
116        return searchResults;
117    }
118    
119    /**
120     * Check if the id object value is correct and transform it if needed
121     * @param remoteKey the remote key
122     * @param idObjectValue the id object value
123     * @param logger the logger
124     * @return The transformed id if the value is correct, null otherwise
125     */
126    protected String checkAndTransformIdObjectValue(String remoteKey, Object idObjectValue, Logger logger)
127    {
128        Object objectValue = idObjectValue instanceof List<?> ? ((List<?>) idObjectValue).get(0) : idObjectValue;
129        if (objectValue == null || !(objectValue instanceof String))
130        {
131            logger.warn("The content identifier is mandatory but there is no value for the key : " + remoteKey);
132            return null;
133        }
134        
135        return objectValue.toString();
136    }
137
138    /**
139     * Get the scope for LDAP search
140     * @return The scope
141     */
142    protected String getSearchScope()
143    {
144        return (String) getParameterValues().get(__PARAM_LDAP_SCOPE);
145    }
146    
147    /**
148     * Get the LDAP filter
149     * @return the filter
150     */
151    protected String getFilter()
152    {
153        return (String) getParameterValues().get(__PARAM_LDAP_FILTER);
154    }
155    
156    /**
157     * Get the LDAP relative DN
158     * @return the relative DN
159     */
160    protected String getRelativeDN()
161    {
162        return (String) getParameterValues().get(__PARAM_LDAP_RELATIVE_DN);
163    }
164    
165    @Override
166    protected void configureSearchModel()
167    {
168        for (String columnOrCriteria : _columnsAndCriteria)
169        {
170            _searchModelConfiguration.addCriterion(columnOrCriteria);
171            _searchModelConfiguration.addColumn(columnOrCriteria, new I18nizableText(columnOrCriteria), false);
172        }
173    }
174    
175    @Override
176    protected Map<String, Map<String, List<Object>>> getRemoteValues(Map<String, Object> searchParameters, Logger logger)
177    {
178        Map<String, Map<String, List<Object>>> organizedResults = super.getRemoteValues(searchParameters, logger);
179        _ldapHelper.transformTypedAttributes(organizedResults, getContentType(), getMapping().keySet());
180        return organizedResults;
181    }
182}