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.core.group; 017 018import java.sql.Connection; 019import java.sql.PreparedStatement; 020import java.sql.ResultSet; 021import java.sql.SQLException; 022import java.util.HashMap; 023import java.util.HashSet; 024import java.util.List; 025import java.util.Map; 026import java.util.Set; 027import java.util.stream.Collectors; 028 029import org.apache.avalon.framework.activity.Initializable; 030import org.apache.avalon.framework.component.Component; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034 035import org.ametys.core.cache.AbstractCacheManager; 036import org.ametys.core.cache.Cache; 037import org.ametys.core.datasource.ConnectionHelper; 038import org.ametys.core.group.directory.GroupDirectory; 039import org.ametys.core.ui.Callable; 040import org.ametys.runtime.config.Config; 041import org.ametys.runtime.i18n.I18nizableText; 042import org.ametys.runtime.plugin.component.AbstractLogEnabled; 043 044/** 045 * Helper for associating {@link GroupDirectory}(ies) to contexts. 046 */ 047public class GroupDirectoryContextHelper extends AbstractLogEnabled implements Component, Serviceable, Initializable 048{ 049 /** Avalon Role */ 050 public static final String ROLE = GroupDirectoryContextHelper.class.getName(); 051 052 /** The "admin" context */ 053 public static final String ADMIN_CONTEXT = "/admin"; 054 055 /** The name of the JDBC table for group directories by context */ 056 private static final String __GROUP_DIRECTORIES_TABLE = "GroupDirectoriesByContext"; 057 058 private static final String __GROUP_DIRECTORIES_BY_CONTEXT_CACHE = "groupDirectoriesByContext"; 059 060 private static final String IS_CACHE_FILLED = "###iscachefilled###"; 061 062 063 /** The DAO for {@link GroupDirectory}(ies) */ 064 private GroupDirectoryDAO _groupDirectoryDAO; 065 066 private AbstractCacheManager _abstractCacheManager; 067 068 069 @Override 070 public void service(ServiceManager manager) throws ServiceException 071 { 072 _groupDirectoryDAO = (GroupDirectoryDAO) manager.lookup(GroupDirectoryDAO.ROLE); 073 _abstractCacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 074 } 075 076 @Override 077 public void initialize() throws Exception 078 { 079 _abstractCacheManager.createMemoryCache(__GROUP_DIRECTORIES_BY_CONTEXT_CACHE, 080 new I18nizableText("plugin.core", "PLUGINS_CORE_CACHE_GROUP_DIRECTORY_CONTEXT_LABEL"), 081 new I18nizableText("plugin.core", "PLUGINS_CORE_CACHE_GROUP_DIRECTORY_CONTEXT_DESCRIPTION"), 082 true, 083 null); 084 } 085 086 /** 087 * Get the connection to the database 088 * @return the SQL connection 089 */ 090 protected Connection getSQLConnection () 091 { 092 String datasourceId = Config.getInstance().getValue("runtime.assignments.groups"); 093 return ConnectionHelper.getConnection(datasourceId); 094 } 095 096 /** 097 * Links given group directories to a context. 098 * @param context The context 099 * @param ids The ids of the group directories to link 100 * @return The ids of the linked group directories 101 */ 102 @SuppressWarnings("resource") 103 @Callable 104 public Set<String> link(String context, List<String> ids) 105 { 106 Set<String> result = getGroupDirectoriesOnContext(context); 107 108 Connection connection = null; 109 PreparedStatement stmt = null; 110 try 111 { 112 connection = getSQLConnection(); 113 114 // Remove all the ids affected to this context 115 String sql = "DELETE FROM " + __GROUP_DIRECTORIES_TABLE + " WHERE Context=?"; 116 stmt = connection.prepareStatement(sql); 117 stmt.setString(1, context); 118 stmt.executeUpdate(); 119 getLogger().info("{}\n[{}]", sql, context); 120 121 // Set the new ids to this context 122 sql = "INSERT INTO " + __GROUP_DIRECTORIES_TABLE + " (Context, GroupDirectory_Id) VALUES(?, ?)"; 123 stmt = connection.prepareStatement(sql); 124 for (String id : ids) 125 { 126 if (_groupDirectoryDAO.getGroupDirectory(id) != null) 127 { 128 stmt.setString(1, context); 129 stmt.setString(2, id); 130 stmt.executeUpdate(); 131 132 getLogger().info("{}\n[{}, {}]", sql, context, id); 133 if (result.contains(id)) 134 { 135 result.remove(id); // the group directory was already linked, so its status didn't changed 136 } 137 else 138 { 139 result.add(id); 140 } 141 } 142 else 143 { 144 getLogger().warn("The GroupDirectory with id '{}' does not exist. It will not be linked.", id); 145 } 146 } 147 } 148 catch (SQLException e) 149 { 150 getLogger().error("Error in sql query", e); 151 } 152 finally 153 { 154 ConnectionHelper.cleanup(connection); 155 ConnectionHelper.cleanup(stmt); 156 _getCache().invalidateAll(); 157 } 158 159 return result; 160 } 161 162 /** 163 * Gets the group directories linked to the given context 164 * @param context The context 165 * @return The ids of group directories linked to the context 166 */ 167 @Callable 168 public Set<String> getGroupDirectoriesOnContext(String context) 169 { 170 if (ADMIN_CONTEXT.equals(context)) 171 { 172 // Return all the group directories 173 return _groupDirectoryDAO.getGroupDirectoriesIds(); 174 } 175 else 176 { 177 return _getDirectoriesOnContextFromDatabase(context); 178 } 179 } 180 181 /** 182 * Gets the group directories linked to at least one of the given contexts 183 * @param contexts The contexts 184 * @return The ids of group directories linked to the contexts 185 */ 186 @Callable 187 public Set<String> getGroupDirectoriesOnContexts(Set<String> contexts) 188 { 189 return contexts.stream() 190 .map(this::getGroupDirectoriesOnContext) 191 .flatMap(Set::stream) 192 .collect(Collectors.toSet()); 193 } 194 195 private Set<String> _getDirectoriesOnContextFromDatabase(String context) 196 { 197 return _getCacheAsMap().computeIfAbsent(context, str -> new HashSet<>()); 198 } 199 200 /** 201 * create a group directory map from the database 202 * @return Non null map of group directory content 203 */ 204 private synchronized Map<String, Set<String>> _getAllGroupDirectoryContent() 205 { 206 Map<String, Set<String>> cache = new HashMap<>(); 207 208 Connection connection = null; 209 PreparedStatement stmt = null; 210 ResultSet rs = null; 211 try 212 { 213 connection = getSQLConnection(); 214 215 String sql = "SELECT Context, GroupDirectory_Id FROM " + __GROUP_DIRECTORIES_TABLE; 216 stmt = connection.prepareStatement(sql); 217 rs = stmt.executeQuery(); 218 219 while (rs.next()) 220 { 221 String context = rs.getString(1); 222 String groupDirectoryId = rs.getString(2); 223 224 if (_groupDirectoryDAO.getGroupDirectory(groupDirectoryId) == null) 225 { 226 getLogger().warn("The group directory of id '{}' is linked to a context, but does not exist anymore.", groupDirectoryId); 227 } 228 else 229 { 230 Set<String> cacheForContext = cache.computeIfAbsent(context, c -> new HashSet<>()); 231 232 cacheForContext.add(groupDirectoryId); 233 } 234 } 235 } 236 catch (SQLException e) 237 { 238 throw new IllegalStateException("Cannot get group directories and contexts association because of an error in a sql query", e); 239 240 } 241 finally 242 { 243 ConnectionHelper.cleanup(connection); 244 ConnectionHelper.cleanup(stmt); 245 ConnectionHelper.cleanup(rs); 246 } 247 return cache; 248 } 249 250 /** 251 * Get the cache 252 * @return Non null cache 253 */ 254 protected synchronized Cache<String, Set<String>> _getCache() 255 { 256 Cache<String, Set<String>> cache = _abstractCacheManager.get(__GROUP_DIRECTORIES_BY_CONTEXT_CACHE); 257 258 if (!cache.hasKey(IS_CACHE_FILLED)) 259 { 260 Map<String, Set<String>> cacheMap = _getAllGroupDirectoryContent(); 261 cache.putAll(cacheMap); 262 cache.put(IS_CACHE_FILLED, null); 263 } 264 return cache; 265 } 266 267 /** 268 * Get the cache as map 269 * @return Non null cache 270 */ 271 protected synchronized Map<String, Set<String>> _getCacheAsMap() 272 { 273 Map<String, Set<String>> cacheAsMap = _getCache().asMap(); 274 cacheAsMap.remove(IS_CACHE_FILLED); 275 return cacheAsMap; 276 } 277}