001/* 002 * Copyright 2012 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.right; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.HashSet; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024 025import org.apache.avalon.framework.activity.Initializable; 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.configuration.Configuration; 028import org.apache.avalon.framework.configuration.ConfigurationException; 029import org.apache.avalon.framework.logger.AbstractLogEnabled; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.avalon.framework.thread.ThreadSafe; 034import org.apache.commons.lang3.StringUtils; 035import org.xml.sax.ContentHandler; 036import org.xml.sax.SAXException; 037 038import org.ametys.core.ui.Callable; 039import org.ametys.core.util.I18nUtils; 040import org.ametys.runtime.i18n.I18nizableText; 041import org.ametys.runtime.plugin.ExtensionPoint; 042 043 044/** 045 * This extension point handle a list of rights handled by the plugins or the application. 046 */ 047public class RightsExtensionPoint extends AbstractLogEnabled implements ExtensionPoint<Right>, Initializable, ThreadSafe, Component, Serviceable 048{ 049 /** The avalon role */ 050 public static final String ROLE = RightsExtensionPoint.class.getName(); 051 /** The i18n utils */ 052 protected I18nUtils _i18nUtils; 053 054 /** The map of rightId, Right of declared rights */ 055 protected Map<String, Right> _rights; 056 057 public void service(ServiceManager manager) throws ServiceException 058 { 059 _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 060 } 061 062 public void initialize() throws Exception 063 { 064 _rights = new HashMap<>(); 065 } 066 067 public boolean hasExtension(String id) 068 { 069 return _rights.containsKey(id); 070 } 071 072 public void addExtension(String id, String pluginName, String pluginId, Configuration configuration) throws ConfigurationException 073 { 074 if (getLogger().isDebugEnabled()) 075 { 076 getLogger().debug("Adding rights from plugin " + pluginName + "/" + pluginId); 077 } 078 079 Configuration[] rightsConfigurations = configuration.getChildren("right"); 080 for (Configuration rightConfiguration : rightsConfigurations) 081 { 082 try 083 { 084 addRight("plugin." + pluginName, rightConfiguration, "Declared by plugin '" + pluginName + "'"); 085 } 086 catch (ConfigurationException e) 087 { 088 if (getLogger().isWarnEnabled()) 089 { 090 getLogger().warn("The plugin '" + pluginName + "." + pluginId + "' has a rights extension but has an incorrect configuration", e); 091 } 092 } 093 } 094 } 095 096 /** 097 * Declare a new right (not as used) 098 * @param defaultCatalog The default catalog. 099 * @param configuration The configuration of the extension 100 * @param message Declaration origin (for debug purpose) 101 * @return The created right 102 * @throws ConfigurationException if configuration if not complete 103 */ 104 protected Right addRight(String defaultCatalog, Configuration configuration, String message) throws ConfigurationException 105 { 106 String id = configuration.getAttribute("id", ""); 107 if (id.length() == 0) 108 { 109 throw new ConfigurationException("Right declaration is incorrect since no 'Id' attribute is specified (or may be empty)", configuration); 110 } 111 112 I18nizableText i18nLabel = I18nizableText.parseI18nizableText(configuration.getChild("label", false), defaultCatalog); 113 if (i18nLabel == null) 114 { 115 throw new ConfigurationException("Right declaration is incorrect since no 'label' element is specified (or may be empty)", configuration); 116 } 117 118 I18nizableText i18nDescription = I18nizableText.parseI18nizableText(configuration.getChild("description", false), defaultCatalog); 119 if (i18nDescription == null) 120 { 121 throw new ConfigurationException("Right declaration is incorrect since no 'description' element is specified (or may be empty)", configuration); 122 } 123 124 I18nizableText i18nCategory = I18nizableText.parseI18nizableText(configuration.getChild("category", false), defaultCatalog); 125 if (i18nCategory == null) 126 { 127 throw new ConfigurationException("Right declaration is incorrect since no 'category' element is specified (or may be empty)", configuration); 128 } 129 130 if (_rights.containsKey(id)) 131 { 132 Right right = _rights.get(id); 133 throw new ConfigurationException("Right with id '" + id + "' is already declared : '" + right.getDeclaration() + "'. This second declaration is ignored.", configuration); 134 } 135 136 if (getLogger().isDebugEnabled()) 137 { 138 getLogger().debug("Adding right ID : " + id); 139 } 140 141 Right right = new Right(id, i18nLabel, i18nDescription, i18nCategory, message); 142 if (_rights.containsKey(id)) 143 { 144 Right oldright = _rights.get(id); 145 throw new IllegalArgumentException("Right with id '" + id + "' is already declared : '" + oldright.getDeclaration() + "'. This second declaration is ignored."); 146 } 147 _rights.put(id, right); 148 149 return right; 150 } 151 152 /** 153 * Declare a new right as used. Use this method to add a right programmatically. 154 * @param defaultCatalog The default catalog. 155 * @param configuration The configuration of the extension 156 * @return The created right 157 * @throws ConfigurationException if configuration if not complete 158 */ 159 public Right addRight(String defaultCatalog, Configuration configuration) throws ConfigurationException 160 { 161 return addRight(defaultCatalog, configuration, "Declared by API"); 162 } 163 164 /** 165 * Declare a new right as used. Use this method to add a right programmatically. 166 * @param id The id of the right (not null or empty) 167 * @param labelKey The label of the right (i18n key) (not null or empty) 168 * @param descriptionKey The description of the right (i18n key) (not null or empty) 169 * @param categoryKey The category of the right (i18n key) (not null or empty) 170 * @return The created right 171 * @throws IllegalArgumentException if the id is already declared 172 */ 173 public Right addRight(String id, I18nizableText labelKey, I18nizableText descriptionKey, I18nizableText categoryKey) throws IllegalArgumentException 174 { 175 if (getLogger().isDebugEnabled()) 176 { 177 getLogger().debug("Adding right from API with ID : " + id); 178 } 179 180 if (_rights.containsKey(id)) 181 { 182 Right right = _rights.get(id); 183 throw new IllegalArgumentException("Right with id '" + id + "' is already declared : '" + right.getDeclaration() + "'. The application cannot be run."); 184 } 185 186 Right right = new Right(id, labelKey, descriptionKey, categoryKey, "Declared by API"); 187 _rights.put(id, right); 188 189 return right; 190 } 191 192 /** 193 * Remove a right from the list of used rights 194 * @param id id of the right to remove 195 */ 196 public void removeRight(String id) 197 { 198 if (getLogger().isDebugEnabled()) 199 { 200 getLogger().debug("Removing right with ID : " + id + "from API"); 201 } 202 203 _rights.remove(id); 204 } 205 206 public Right getExtension(String id) 207 { 208 return _rights.get(id); 209 } 210 211 public Set<String> getExtensionsIds() 212 { 213 return new HashSet<>(_rights.keySet()); 214 } 215 216 public void initializeExtensions() throws Exception 217 { 218 // empty 219 } 220 221 /** 222 * SAX all managed rights 223 * 224 * @param handler the handler receiving SAX events 225 * @throws SAXException if something wrong occurs 226 */ 227 public void toSAX (ContentHandler handler) throws SAXException 228 { 229 for (String id : _rights.keySet()) 230 { 231 Right right = _rights.get(id); 232 right.toSAX(handler); 233 } 234 } 235 236 /** 237 * Search a right 238 * @param rightQuery the query or empty for all rights 239 * @param includeReader true to include a fake reader right in results 240 * @return a list of rights as JSON 241 */ 242 @Callable(rights = Callable.NO_CHECK_REQUIRED) 243 public List<Map<String, Object>> search(String rightQuery, boolean includeReader) 244 { 245 List<Map<String, Object>> rights = new ArrayList<>(); 246 247 if (includeReader) 248 { 249 String id = "READER"; 250 I18nizableText label = new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHTS_READER_LABEL"); 251 I18nizableText description = new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHTS_READER_DESCRIPTION"); 252 253 // Include the reader right only if no filter or match filter 254 if (StringUtils.isEmpty(rightQuery) || _matchesFilter(id, label, description, rightQuery)) 255 { 256 Map<String, Object> right = new HashMap<>(); 257 right.put("id", id); 258 right.put("label", label); 259 right.put("description", description); 260 right.put("category", new I18nizableText("plugin.core", "PLUGINS_CORE_RIGHTS_READER_CATEGORY")); 261 rights.add(right); 262 } 263 } 264 265 for (String rightId : getExtensionsIds()) 266 { 267 Right right = getExtension(rightId); 268 269 if (StringUtils.isEmpty(rightQuery) || _matchesFilter(rightId, right.getLabel(), right.getDescription(), rightQuery)) 270 { 271 rights.add(right.toJSON()); 272 } 273 } 274 275 return rights; 276 } 277 278 private boolean _matchesFilter(String id, I18nizableText label, I18nizableText description, String filter) 279 { 280 String normalizedFilter = StringUtils.stripAccents(filter.toLowerCase()); 281 282 String normalizedId = id.toLowerCase(); 283 String normalizedLabel = StringUtils.stripAccents(StringUtils.defaultString(_i18nUtils.translate(label)).toLowerCase()); 284 String normalizedDescription = StringUtils.stripAccents(StringUtils.defaultString(_i18nUtils.translate(description)).toLowerCase()); 285 286 return normalizedId.contains(normalizedFilter) 287 || normalizedLabel.contains(normalizedFilter) 288 || normalizedDescription.contains(normalizedFilter); 289 } 290}