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}