001/*
002 *  Copyright 2014 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.cms.tag;
017
018import java.text.Normalizer;
019import java.util.ArrayList;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.avalon.framework.component.Component;
026import org.apache.avalon.framework.logger.AbstractLogEnabled;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.commons.lang3.StringUtils;
031
032import org.ametys.core.ui.Callable;
033import org.ametys.core.util.I18nUtils;
034import org.ametys.runtime.i18n.I18nizableText;
035
036/**
037 * DAO for manipulating tags
038 */
039public abstract class AbstractTagsDAO extends AbstractLogEnabled implements Serviceable, Component
040{
041    /** The tag provider extension point */
042    protected AbstractTagProviderExtensionPoint<? extends Tag> _tagProviderExtPt;
043    /** The I18n utils */
044    protected I18nUtils _i18nUtils;
045    
046    @SuppressWarnings("unchecked")
047    @Override
048    public void service(ServiceManager manager) throws ServiceException
049    {
050        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
051        _tagProviderExtPt = (AbstractTagProviderExtensionPoint< ? extends Tag>) manager.lookup(getTagProviderEPRole());
052    }
053    
054    /**
055     * Get the tag provider extension point role
056     * @return the tag provider extension point role
057     */
058    public abstract String getTagProviderEPRole();
059    
060    /**
061     * Get the paths of given tags
062     * @param tagNames The name of tags
063     * @param contextualParameters Contextual parameters
064     * @return {String} path
065     */
066    @Callable
067    public List<String> getTagPaths(List<String> tagNames, Map<String, Object> contextualParameters)
068    {
069        List<String> paths = new ArrayList<>();
070        if (tagNames != null)
071        {
072            for (String tagName : tagNames)
073            {
074                if (tagName.startsWith("provider_"))
075                {
076                    paths.add(tagName);
077                }
078                else
079                {
080                    String tagPath = getFullPath(tagName, contextualParameters);
081                    if (tagPath != null)
082                    {
083                        paths.add(tagPath);
084                    }
085                    
086                }
087            }
088        }
089        
090        return paths;
091    }
092
093    /**
094     * Get the full path of a tag, with its provider
095     * @param tagName The tag name
096     * @param contextualParameters Contextual parameters
097     * @return the full path of tag or null if not found
098     */
099    protected String getFullPath (String tagName, Map<String, Object> contextualParameters)
100    {
101        Set<String> tagProviders = _tagProviderExtPt.getExtensionsIds();
102        
103        for (String id : tagProviders)
104        {
105            TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(id);
106            if (tagProvider.hasTag(tagName, contextualParameters))
107            {
108                Tag tag = tagProvider.getTag(tagName, contextualParameters);
109                return "provider_" + tagProvider.getId() + getPath(tag);
110            }
111        }
112        
113        // Not found
114        return null;
115    }
116    
117    /**
118     * Get a tag by its name
119     * @param tagName The tag name
120     * @param contextualParameters Contextual parameters
121     * @return The tag or null if not found
122     */
123    public Tag getTag (String tagName, Map<String, Object> contextualParameters)
124    {
125        Set<String> tagProviders = _tagProviderExtPt.getExtensionsIds();
126        
127        for (String id : tagProviders)
128        {
129            TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(id);
130            if (tagProvider.hasTag(tagName, contextualParameters))
131            {
132                return tagProvider.getTag(tagName, contextualParameters);
133            }
134        }
135        return null;
136    }
137    
138    /**
139     * Get the path of a tag inside its provider.
140     * The path is composed of tags name and '/' as a separator
141     * @param tag The tag
142     * @return The path
143     */
144    protected String getPath (Tag tag)
145    {
146        return tag == null ? "" : getPath(tag.getParent()) + "/" + tag.getName();
147    }
148    
149    /**
150     * Get the title of given tags
151     * @param tagNames The name of tags
152     * @param contextualParameters Contextual parameters
153     * @return the labels
154     */
155    @Callable
156    public Map<String, Object> getTagsTitle(List<String> tagNames, Map<String, Object> contextualParameters)
157    {
158        Map<String, Object> result = new HashMap<>();
159        
160        Set<String> tagProviders = _tagProviderExtPt.getExtensionsIds();
161        
162        List<I18nizableText> labels = new ArrayList<>();
163        
164        for (String tagName : tagNames)
165        {
166            if (tagName.startsWith("provider_"))
167            {
168                TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(tagName.substring("provider_".length()));
169                labels.add(tagProvider.getLabel());
170            }
171            else
172            {
173                for (String id : tagProviders)
174                {
175                    TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(id);
176                    if (tagProvider.hasTag(tagName, contextualParameters))
177                    {
178                        Tag tag = tagProvider.getTag(tagName, contextualParameters);
179                        labels.add(tag.getTitle());
180                    }
181                }
182            }
183        }
184        
185        result.put("titles", labels);
186        return result;
187    }
188
189    /**
190     * Get the path of node which match filter regexp
191     * @param value the value to match
192     * @param contextualParameters Contextual parameters
193     * @return the matching paths
194     */
195    @Callable
196    public List<String> filterTagsByRegExp(String value, Map<String, Object> contextualParameters)
197    {
198        List<String> matchingPaths = new ArrayList<>();
199
200        String toMatch = Normalizer.normalize(value.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "").trim();
201        
202        Set<String> extensionsIds = _tagProviderExtPt.getExtensionsIds();
203        for (String id : extensionsIds)
204        {
205            TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(id);
206            for (Tag tag : tagProvider.getTags(contextualParameters).values())
207            {
208                _getMatchingTag(toMatch, tagProvider, tag, matchingPaths);
209            }
210        }
211        
212        return matchingPaths;
213    }
214
215    /**
216     * Get the path of node which match filter regexp
217     * @param filter the value to match
218     * @param tagNames the list of tag's name to search inside
219     * @param contextualParameters Contextual parameters
220     * @return the matching paths
221     */
222    @Callable
223    public List<String> filterTagsFromListByRegExp(String filter, List<String> tagNames, Map<String, Object> contextualParameters)
224    {
225        if (StringUtils.isEmpty(filter))
226        {
227            return getTagPaths(tagNames, contextualParameters);
228        }
229        
230        if (tagNames != null && tagNames.size() > 0)
231        {
232            List<String> matchingPaths = new ArrayList<>();
233            
234            String toMatch = Normalizer.normalize(filter.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "").trim();
235            
236            for (String tagName : tagNames)
237            {
238                Tag tag = getTag(tagName, contextualParameters);
239                if (tag != null)
240                {
241                    String title = _i18nUtils.translate(tag.getTitle());
242                    String normalizedName = Normalizer.normalize(title.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "");
243                    if (normalizedName.contains(toMatch))
244                    {
245                        matchingPaths.add(getFullPath(tagName, contextualParameters));
246                    }
247                }
248            }
249            
250            return matchingPaths;
251        }
252        else
253        {
254            return filterTagsByRegExp(filter, contextualParameters);
255        }
256    }
257
258    /**
259     * Get paths of tag which match filter regexp
260     * @param value the value to match
261     * @param tagProvider the tag provider
262     * @param tag the current tag
263     * @param matchingPaths the matching paths
264     */
265    private void _getMatchingTag(String value, TagProvider<? extends Tag> tagProvider, Tag tag, List<String> matchingPaths)
266    {
267        String title = _i18nUtils.translate(tag.getTitle());
268        
269        if (title != null)
270        {
271            String normalizedName = Normalizer.normalize(title.toLowerCase(), Normalizer.Form.NFD).replaceAll("[\\p{InCombiningDiacriticalMarks}]", "");
272            
273            if (normalizedName.contains(value))
274            {
275                matchingPaths.add("provider_" + tagProvider.getId() + getPath(tag));
276            }
277        }
278        
279        for (Tag child : tag.getTags().values())
280        {
281            _getMatchingTag(value, tagProvider, child, matchingPaths);
282        }
283    }
284    
285    /**
286     * Test if tags exists
287     * @param tagNames The tag names to test
288     * @param onlyCustomTags If true, return only custom tags
289     * @param otherParameters the other parameters
290     * @param contextualParameters Contextual parameters
291     * @return The list of tag names without the invalid values. 
292     */
293    @Callable
294    public List<String> checkTags(List<String> tagNames, boolean onlyCustomTags, Map<String, Object> otherParameters, Map<String, Object> contextualParameters)
295    {
296        List<String> existingTagNames = new ArrayList<>();
297        List<TagProvider<? extends Tag>> tagProviders = new ArrayList<>();
298        
299        if (onlyCustomTags)
300        {
301            tagProviders.addAll(getCustomTagProvider());
302        }
303        else
304        {
305            Set<String> extensionsIds = _tagProviderExtPt.getExtensionsIds();
306            for (String id : extensionsIds)
307            {
308                tagProviders.add(_tagProviderExtPt.getExtension(id));
309            }
310        }
311        
312        for (String tagName : tagNames)
313        {
314            if (tagName.startsWith("provider_"))
315            {
316                String providerId = tagName.substring("provider_".length());
317                TagProvider<? extends Tag> tagProvider = _tagProviderExtPt.getExtension(providerId);
318                
319                if (tagProvider != null)
320                {
321                    existingTagNames.add(tagName);
322                }
323            }
324            else
325            {
326                for (TagProvider<? extends Tag> tagProvider : tagProviders)
327                {
328                    if (tagProvider.hasTag(tagName, contextualParameters))
329                    {
330                        String filteredTagName = _getFilteredTagName(tagProvider, tagName, otherParameters, contextualParameters);
331                        if (StringUtils.isNotBlank(filteredTagName))
332                        {
333                            existingTagNames.add(filteredTagName);
334                            break;
335                        }
336                    }
337                }
338            }
339        }
340        
341        return existingTagNames;
342    }
343    
344    /**
345     * Get filtered tag name
346     * @param tagProvider the tag provider
347     * @param tagName the tag name
348     * @param otherParameters the other parameters
349     * @param contextualParameters the contextual parameters
350     * @return the tag name if it match
351     */
352    protected String _getFilteredTagName(TagProvider<? extends Tag> tagProvider, String tagName, Map<String, Object> otherParameters, Map<String, Object> contextualParameters)
353    {
354        return tagName;
355    }
356    
357    /**
358     * Get the list of custom tag provider
359     * @return the list of custom tag provider
360     */
361    protected abstract List<TagProvider<? extends Tag>> getCustomTagProvider();
362}