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> searchParameters, 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(searchParameters, offset, limit, sort, columns);
076            List<Map<String, Object>> searchResults = _sqlCollectionDAO.search(params , getDataSourceId());
077            result  = _normalizeSearchResult(remoteKey, columns, searchResults, logger);
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 upper-cased
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     * @param logger the logger
130     * @return the normalize search result map
131     */
132    protected Map<String, Map<String, Object>> _normalizeSearchResult(String remoteKey, List<String> columns, List<Map<String, Object>> searchResults, Logger logger)
133    {
134        Map<String, Map<String, Object>> result = new LinkedHashMap<>();
135        
136        for (Map<String, Object> searchResult : searchResults)
137        {
138            Map<String, Object> newSearchResult = _getNormalizedSearchResult(columns, searchResult);
139            
140            Object idObjectValue = newSearchResult.get(remoteKey);
141            String idTransformed = checkAndTransformIdObjectValue(remoteKey, idObjectValue, logger);
142            if (idTransformed != null)
143            {
144                newSearchResult.put(SCC_UNIQUE_ID, idTransformed);
145                newSearchResult.put(remoteKey, idTransformed);
146                result.put(idTransformed, newSearchResult);
147            }
148        }
149        
150        return result;
151    }
152    
153    /**
154     * Check if the id object value is correct and transform it if needed
155     * @param remoteKey the remote key
156     * @param idObjectValue the id object value
157     * @param logger the logger
158     * @return The transformed id if the value is correct, null otherwise
159     */
160    protected String checkAndTransformIdObjectValue(String remoteKey, Object idObjectValue, Logger logger)
161    {
162        if (idObjectValue == null)
163        {
164            logger.warn("The content identifier is mandatory but there is no value for the key : " + remoteKey);
165            return null;
166        }
167        
168        return idObjectValue.toString();
169    }
170    
171    /**
172     * Get normalized search result
173     * Indeed we need to normalize the search result for each database type
174     * For example, with Oracle, the returned SQL column names are uppercased
175     * so we need to retrieve for each returned column its real name for the mapping,
176     * @param columns the columns list
177     * @param searchResult the search result
178     * @return the normalized search result
179     */
180    protected Map<String, Object> _getNormalizedSearchResult(List<String> columns, Map<String, Object> searchResult)
181    {
182        Map<String, Object> normalizedSearchResult = new HashMap<>();
183        for (String key : searchResult.keySet())
184        {
185            //We search the initial column name
186            List<String> filteredValue = columns.stream()
187                                                .filter(c -> c.equalsIgnoreCase(key))
188                                                .collect(Collectors.toList());
189            if (!filteredValue.isEmpty()) //If we find no column, we ignore it (for example Oracle send 'Line' value)
190            {
191                normalizedSearchResult.put(filteredValue.get(0), searchResult.get(key));
192            }
193        }
194        return normalizedSearchResult;
195    }
196    
197    @Override
198    public int getTotalCount(Map<String, Object> parameters, Logger logger)
199    {
200        Map<String, Object> params = _getTotalCounParameters(parameters);
201        return _sqlCollectionDAO.getTotalCount(params , getDataSourceId());
202    }
203    
204    /**
205     * Get the parameters map for mybatis total count
206     * @param parameters the filter parameter
207     * @return the parameter map
208     */
209    protected Map<String, Object> _getTotalCounParameters(Map<String, Object> parameters)
210    {
211        Map<String, Object> params = new HashMap<>();
212        params.put("table", getTableName());
213        params.put("params", _removeEmptyParameters(parameters));
214
215        return params;
216    }
217    
218}