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}