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