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