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.HashMap;
019import java.util.LinkedHashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.stream.Collectors;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.commons.collections4.CollectionUtils;
028import org.slf4j.Logger;
029
030import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollection;
031
032/**
033 * Implementation of {@link SynchronizableContentsCollection} to be synchronized with a LDAP data source
034 */
035public class SQLSynchronizableContentsCollection extends AbstractDataSourceSynchronizableContentsCollection implements Component
036{
037    private static final String __PARAM_SQL_TABLE = "tableName";
038    
039    /** The SQL collection DAO */
040    protected SQLCollectionDAO _sqlCollectionDAO;
041    
042    @Override
043    public void service(ServiceManager smanager) throws ServiceException
044    {
045        super.service(smanager);
046        _sqlCollectionDAO = (SQLCollectionDAO) smanager.lookup(SQLCollectionDAO.ROLE);
047    }
048    
049    /**
050     * Get the name of SQL table
051     * @return The SQL table name
052     */
053    public String getTableName()
054    {
055        return (String) getParameterValues().get(__PARAM_SQL_TABLE);
056    }
057    
058    @Override
059    protected Map<String, Map<String, Object>> internalSearch(Map<String, Object> parameters, int offset, int limit, List<Object> sort, Logger logger)
060    {
061        Map<String, Map<String, Object>> result = new LinkedHashMap<>();
062        
063        Map<String, List<String>> mapping = getMapping();
064        String idField = getIdField();
065        List<String> remoteKeys = mapping.get(idField);
066        if (CollectionUtils.isNotEmpty(remoteKeys))
067        {
068            String remoteKey = remoteKeys.get(0);
069            
070            List<String> columns = mapping.values()
071                                          .stream()
072                                          .flatMap(List::stream)
073                                          .collect(Collectors.toList());
074
075            Map<String, Object> params = _getSearchParameters(parameters, offset, limit, sort, columns);
076            List<Map<String, Object>> searchResults = _sqlCollectionDAO.search(params , getDataSourceId());
077            result  = _normalizeSearchResult(remoteKey, columns, searchResults);
078        }
079        else
080        {
081            _nbError++;
082            logger.error("Missing SQL attribute in mapping for field '{}' holding the unique identifier", idField);
083        }
084        
085        return result;
086    }
087
088    /**
089     * Get the parameters map for mybatis search
090     * @param parameters the filter parameter
091     * @param offset the offset
092     * @param limit the limit
093     * @param sort the sort map
094     * @param columns the list of columns
095     * @return the parameter map
096     */
097    protected Map<String, Object> _getSearchParameters(Map<String, Object> parameters, int offset, int limit, List<Object> sort, List<String> columns)
098    {
099        Map<String, Object> params = new HashMap<>();
100        params.put("table", getTableName());
101        params.put("columns", columns);
102        params.put("params", parameters);
103        
104        if (offset > 0)
105        {
106            params.put("offset", offset);
107        }
108        
109        if (limit < Integer.MAX_VALUE)
110        {
111            params.put("limit", limit);
112        }
113        
114        if (CollectionUtils.isNotEmpty(sort))
115        {
116            params.put("sorts", sort);
117        }
118        
119        return params;
120    }
121
122    /**
123     * We need to normalize the search result for each database type
124     * For example, with Oracle, the returned SQL column names are uppercased
125     * so we need to retrieve for each returned column its real name for the mapping, 
126     * @param remoteKey the remote key handling the id
127     * @param columns the list of columns
128     * @param searchResults the search result map
129     * @return the normalize search result map
130     */
131    protected Map<String, Map<String, Object>> _normalizeSearchResult(String remoteKey, List<String> columns, List<Map<String, Object>> searchResults)
132    {
133        Map<String, Map<String, Object>> result = new LinkedHashMap<>();
134        
135        for (Map<String, Object> searchResult : searchResults)
136        {
137            Map<String, Object> newSearchResult = _getNormalizedSearchResult(columns, searchResult);
138            
139            Object idObjectValue = newSearchResult.get(remoteKey);
140            newSearchResult.put(SCC_UNIQUE_ID, idObjectValue.toString());
141            result.put(idObjectValue.toString(), newSearchResult);
142        }
143        
144        return result;
145    }
146
147    /**
148     * Get normalized search result
149     * Indeed we need to normalize the search result for each database type
150     * For example, with Oracle, the returned SQL column names are uppercased
151     * so we need to retrieve for each returned column its real name for the mapping, 
152     * @param columns the columns list
153     * @param searchResult the search result
154     * @return the normalized search result
155     */
156    protected Map<String, Object> _getNormalizedSearchResult(List<String> columns, Map<String, Object> searchResult)
157    {
158        Map<String, Object> normalizedSearchResult = new HashMap<>();
159        for (String key : searchResult.keySet())
160        {
161            //We search the initial column name
162            List<String> filteredValue = columns.stream()
163                                                .filter(c -> c.equalsIgnoreCase(key))
164                                                .collect(Collectors.toList());
165            if (!filteredValue.isEmpty()) //If we find no column, we ignore it (for example Oracle send 'Line' value) 
166            {
167                normalizedSearchResult.put(filteredValue.get(0), searchResult.get(key));
168            }
169        }
170        return normalizedSearchResult;
171    }
172    
173    @Override
174    public int getTotalCount(Map<String, Object> parameters, Logger logger)
175    {
176        Map<String, Object> params = _getTotalCounParameters(parameters);
177        return _sqlCollectionDAO.getTotalCount(params , getDataSourceId());
178    }
179    
180    /**
181     * Get the parameters map for mybatis total count
182     * @param parameters the filter parameter
183     * @return the parameter map
184     */
185    protected Map<String, Object> _getTotalCounParameters(Map<String, Object> parameters)
186    {
187        Map<String, Object> params = new HashMap<>();
188        params.put("table", getTableName());
189        params.put("params", _removeEmptyParameters(parameters));
190
191        return params;
192    }
193    
194}