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 */
016
017package org.ametys.core.util;
018
019import java.util.ArrayList;
020import java.util.Collection;
021import java.util.Date;
022import java.util.HashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026
027import org.apache.avalon.framework.context.Context;
028import org.apache.avalon.framework.context.ContextException;
029import org.apache.avalon.framework.context.Contextualizable;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.cocoon.components.ContextHelper;
034import org.apache.cocoon.environment.Request;
035import org.apache.cocoon.xml.dom.DOMBuilder;
036import org.apache.commons.lang.StringEscapeUtils;
037import org.apache.commons.lang3.StringUtils;
038import org.slf4j.Logger;
039import org.slf4j.LoggerFactory;
040import org.w3c.dom.Node;
041import org.w3c.dom.NodeList;
042import org.xml.sax.SAXException;
043
044import org.ametys.core.DevMode;
045import org.ametys.core.DevMode.DEVMODE;
046import org.ametys.core.group.Group;
047import org.ametys.core.group.GroupIdentity;
048import org.ametys.core.group.GroupManager;
049import org.ametys.core.right.RightManager;
050import org.ametys.core.right.RightManager.RightResult;
051import org.ametys.core.user.CurrentUserProvider;
052import org.ametys.core.user.User;
053import org.ametys.core.user.UserIdentity;
054import org.ametys.core.util.dom.AmetysNodeList;
055import org.ametys.core.util.dom.MapElement;
056import org.ametys.core.util.dom.StringElement;
057import org.ametys.core.version.Version;
058import org.ametys.core.version.VersionsHandler;
059import org.ametys.plugins.core.user.UserHelper;
060import org.ametys.runtime.config.Config;
061import org.ametys.runtime.i18n.I18nizableText;
062import org.ametys.runtime.servlet.RuntimeConfig;
063import org.ametys.runtime.workspace.WorkspaceManager;
064import org.ametys.runtime.workspace.WorkspaceMatcher;
065
066/**
067 * Helper component to be used from XSL stylesheets.
068 */
069public class AmetysXSLTHelper implements Contextualizable, Serviceable
070{
071    /** The logger */
072    protected static final Logger _LOGGER = LoggerFactory.getLogger(AmetysXSLTHelper.class.getName());
073    
074    /** The i18n utils instance */
075    protected static I18nUtils _i18nUtils;
076    
077    /** The versions handler */
078    protected static VersionsHandler _versionHandler;
079
080    /** The current user provider */
081    protected static CurrentUserProvider _currentUserProvider;
082    /** The groups manager */
083    protected static GroupManager _groupManager;
084    /** The user helper */
085    protected static UserHelper _userHelper;
086    /** The json utils */
087    protected static JSONUtils _jsonUtils;
088    /** The right manager */
089    protected static RightManager _rightManager;
090    
091    private static Context _context;
092
093    @Override
094    public void contextualize(Context context) throws ContextException
095    {
096        _context = context;
097    }
098    
099    public void service(ServiceManager manager) throws ServiceException
100    {
101        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
102        _versionHandler = (VersionsHandler) manager.lookup(VersionsHandler.ROLE);
103        
104        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
105        _groupManager = (GroupManager) manager.lookup(GroupManager.ROLE);
106        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
107        _jsonUtils = (JSONUtils) manager.lookup(JSONUtils.ROLE);
108        _rightManager = (RightManager) manager.lookup(RightManager.ROLE);
109    }
110    
111    /**
112     * Returns the current URI prefix.
113     * @return the current URI prefix.
114     */
115    public static String uriPrefix()
116    {
117        return uriPrefix(true);
118    }
119    
120    /**
121     * Returns the current URI prefix.
122     * @param withWorkspaceURI true to add the workspace URI (recommended)
123     * @return the current URI prefix.
124     */
125    public static String uriPrefix(boolean withWorkspaceURI)
126    {
127        return getUriPrefix(withWorkspaceURI);
128    }
129    
130    /**
131     * Returns the absolute URI prefix.
132     * @return the absolute URI prefix.
133     */
134    public static String absoluteUriPrefix()
135    {
136        return absoluteUriPrefix(true);
137    }
138    
139    /**
140     * Returns the absolute URI prefix.
141     * @param withWorkspaceURI true to add the workspace URI (recommended)
142     * @return the absolute URI prefix.
143     */
144    public static String absoluteUriPrefix(boolean withWorkspaceURI)
145    {
146        return getAbsoluteUriPrefix(withWorkspaceURI);
147    }
148    
149    /**
150     * Return the current workspace name
151     * @return The workspace name. Cannot be empty.
152     */
153    public static String workspaceName()
154    {
155        return getWorkspaceName();
156    }
157    
158    /**
159     * Return the current workspace URI
160     * @return The workspace name. Can be empty.
161     */
162    public static String workspacePrefix()
163    {
164        return getWorkspacePrefix();
165    }
166    
167    /**
168     * Return the current workspace theme name
169     * @return The name
170     */
171    public static String workspaceTheme()
172    {
173        Request request = ContextHelper.getRequest(_context);
174        return (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_THEME);
175    }
176
177    /**
178     * Return the current workspace theme url
179     * @return The url without any prefix
180     */
181    public static String workspaceThemeURL()
182    {
183        Request request = ContextHelper.getRequest(_context);
184        
185        String workspaceThemeUrl = (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_THEME_URL);
186        if (workspaceThemeUrl == null)
187        {
188            // fallback to the default workspace
189            String workspaceName = RuntimeConfig.getInstance().getDefaultWorkspace();
190            WorkspaceManager wm = WorkspaceManager.getInstance();
191            if (wm.getWorkspaceNames().contains(workspaceName))
192            {
193                workspaceThemeUrl = wm.getWorkspaces().get(workspaceName).getThemeURL();
194            }
195        }
196        
197        return workspaceThemeUrl;
198    }
199    
200    /**
201     * Get the application context path. Can be empty if the application
202     * resides in the root context. Use it to create a link beginning with
203     * the application root.
204     * @param withWorkspaceURI true to add the workspace URI (recommended)
205     * @return The application context path with workspace URI
206     * @see Request#getContextPath()
207     */
208    protected static String getUriPrefix(boolean withWorkspaceURI)
209    {
210        Request request = ContextHelper.getRequest(_context);
211        String workspaceURI = withWorkspaceURI ? getWorkspacePrefix() : "";
212        
213        return request.getContextPath() + workspaceURI;
214    }
215    
216    /**
217     * Get the absolutized version of the context path. Use it to create an absolute
218     * link beginning with the application root, for instance when sending a mail
219     * linking to the application.
220     * @param withWorkspaceURI true to add the workspace URI (recommended)
221     * @return The absolute context path.
222     */
223    protected static String getAbsoluteUriPrefix(boolean withWorkspaceURI)
224    {
225        Request request = ContextHelper.getRequest(_context);
226        
227        String uriPrefix = getUriPrefix(withWorkspaceURI);
228        
229        if (!uriPrefix.startsWith("http"))
230        {
231            uriPrefix = request.getScheme() + "://" + request.getServerName() + (request.getServerPort() != 80 ? ":" + request.getServerPort() : "") + uriPrefix;
232        }
233        
234        return uriPrefix;
235    }  
236    
237    /**
238     * Return the current workspace name
239     * @return The workspace name. Cannot be empty.
240     */
241    protected static String getWorkspaceName()
242    {
243        Request request = ContextHelper.getRequest(_context);
244        return (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_NAME);
245    }
246    
247    /**
248     * Return the current workspace URI
249     * @return The workspace name. Can be empty for the default workspace.
250     */
251    protected static String getWorkspacePrefix()
252    {
253        Request request = ContextHelper.getRequest(_context);
254        return (String) request.getAttribute(WorkspaceMatcher.WORKSPACE_URI);
255    }
256
257    /**
258     * Returns the configuration value associated with the given parameter.
259     * @param id the configuration parameter.
260     * @return the configuration value associated with the given parameter.
261     */
262    public static Object config(String id)
263    {
264        if (Config.getInstance() != null)
265        {
266            return Config.getInstance().getValue(id);
267        }
268        else
269        {
270            return null;
271        }
272    }
273    
274    /**
275     * Return the value of a request parameter.
276     * @param parameter the parameter name.
277     * @return the request parameter.
278     */
279    public static String requestParameter(String parameter)
280    {
281        Request request = ContextHelper.getRequest(_context);
282        return request.getParameter(parameter);
283    }
284    
285    /**
286     * Translate an i18n key using current user language.
287     * @param key The key to translate. Specify the catalog this way: "catalogue:KEY"
288     * @return The translation or null.
289     */
290    public static String translate(String key)
291    {
292        return translate(key, null, null);
293    }
294    
295    /**
296     * Translate an i18n key
297     * @param key The key to translate. Specify the catalog this way: "catalogue:KEY"
298     * @param lang The language. Can be null to use current user language.
299     * @param parameters The key parameters. Can be empty.
300     * @return The translation or null.
301     */
302    public static String translate(String key, String lang, NodeList parameters)
303    {
304        List<String> i18nparams = new ArrayList<>();
305        if (parameters != null && parameters.getLength() == 1)
306        {
307            NodeList childNodes = parameters.item(0).getChildNodes();
308            for (int i = 0; i < childNodes.getLength(); i++) 
309            {  
310                i18nparams.add(childNodes.item(i).getTextContent());
311            }
312        }
313        
314        I18nizableText i18nKey = new I18nizableText(null, key, i18nparams);
315        return _i18nUtils.translate(i18nKey, lang);
316    }
317    
318    /**
319     * Escape the given string to be used as JS variable.
320     * @param str the string to escape.
321     * @return the escaped String.
322     */
323    public static String escapeJS(String str)
324    {
325        return StringEscapeUtils.escapeJavaScript(str);
326    }
327    
328    /**
329     * Encode the string to be url compliant
330     * @param url The url to encode
331     * @return The encoded url
332     */
333    public static String urlEncode(String url)
334    {
335        return URLEncoder.encodePath(url);
336    }
337    
338    /**
339     * Split the text.
340     * @param textToSplit the text to split.
341     * @param tokenizers the tokenizer characters.
342     * @param startIndex the minimum number of characters of the result string
343     * @return the split text.
344     */
345    public static String splitText(String textToSplit, String tokenizers, int startIndex)
346    {
347        String tokenizableText = textToSplit.substring(startIndex != 0 ? startIndex - 1 : 0, textToSplit.length());
348        
349        int tokenPlace = StringUtils.indexOfAny(tokenizableText, tokenizers);
350        
351        if (tokenPlace == -1)
352        {
353            return textToSplit;
354        }
355        else
356        {
357            return textToSplit.substring(0, startIndex - 1 + tokenPlace);
358        }
359    }
360    
361    /**
362     * Split the text.
363     * @param textToSplit the text to split.
364     * @param tokenizers the tokenizer characters.
365     * @param maxCharacters the maximum number of characters of the result string
366     * @param currentCharactersNumber the current character number.
367     * @return the split text.
368     */
369    @Deprecated
370    public static String splitText(String textToSplit, String tokenizers, int maxCharacters, int currentCharactersNumber)
371    {
372        int tokenStartIndex = maxCharacters - currentCharactersNumber - 1;
373        String tokenizableText = textToSplit.substring(tokenStartIndex, textToSplit.length());
374        
375        int tokenPlace = StringUtils.indexOfAny(tokenizableText, tokenizers);
376        
377        if (tokenPlace == -1)
378        {
379            return textToSplit;
380        }
381        else
382        {
383            return textToSplit.substring(0, tokenStartIndex + tokenPlace);
384        }
385    }
386    
387    /**
388     * Get the versions of the application.
389     * Default VersionsHandler impl will return Ametys and Application versions
390     * @return The versions &lt;Version&gt;&lt;Version&gt;&lt;Name&gt;X&lt;/Name&gt;&lt;Version&gt;X&lt;/Version&gt;&lt;Date&gt;X&lt;/Date&gt;&lt;/Version&gt;&lt;/Versions&gt; (empty tags are removed)
391     */
392    public static Node versions()
393    {
394        Map<String, Object> versionsMap = new HashMap<>();
395
396        List<Object> versionList = new ArrayList<>();
397
398        for (Version version : _versionHandler.getVersions())
399        {
400            Map<String, Object> versionMap = new HashMap<>();
401
402            String componentName = version.getName();
403            String componentVersion = version.getVersion();
404            String componentDate =  DateUtils.dateToString(version.getDate());
405            
406            if (StringUtils.isNotEmpty(componentName))
407            {
408                versionMap.put("Name", componentName);
409            }
410            if (StringUtils.isNotEmpty(componentVersion))
411            {
412                versionMap.put("Version", componentVersion);
413            }
414            if (StringUtils.isNotEmpty(componentDate))
415            {
416                versionMap.put("Date", componentDate);
417            }
418            
419            versionList.add(versionMap);
420        }
421        
422        versionsMap.put("Component", versionList);
423
424        return new MapElement("Versions", versionsMap);
425    }
426    
427    /**
428     * Get the current mode of the application for the current user.
429     * @return True if the application is in developer mode, false if in production mode.
430     */
431    public static boolean isDeveloperMode()
432    {
433        Request request = ContextHelper.getRequest(_context);
434        
435        DEVMODE developerMode = DevMode.getDeveloperMode(request);
436        return developerMode == DEVMODE.DEVELOPMENT 
437                || developerMode == DEVMODE.SUPER_DEVELOPPMENT;
438    }
439    
440    /**
441     * Return the user
442     * @return The current connected user object or null
443     * @throws SAXException if a problem occured while getting the user
444     */
445    public static Node user() throws SAXException
446    {
447        UserIdentity userIdentity = _currentUserProvider.getUser();
448        if (userIdentity != null)
449        {
450            return user(userIdentity.getLogin(), userIdentity.getPopulationId());
451        }
452        
453        return null;
454    }
455    
456    /**
457     * Return the given user
458     * @param userIdentity the concerned user's login + population
459     * @return The informations about the given user
460     * @throws SAXException If an error occurred while saxing the user
461     */
462    public static Node user(String userIdentity) throws SAXException
463    {
464        UserIdentity userIdentityObject = UserIdentity.stringToUserIdentity(userIdentity);
465        if (userIdentityObject == null)
466        {
467            return null;
468        }
469        else
470        {
471            return user(userIdentityObject.getLogin(), userIdentityObject.getPopulationId());
472        }
473    }
474    
475    /**
476     * Return the given user
477     * @param login the concerned user's login
478     * @param populationId the concerned user's population id
479     * @return The informations about the given user
480     * @throws SAXException If an error occurred while saxing the user
481     */
482    public static Node user(String login, String populationId) throws SAXException
483    {
484        DOMBuilder domBuilder = new DOMBuilder();
485        
486        UserIdentity userIdentity = new UserIdentity(login, populationId);
487        _userHelper.saxUserIdentity(userIdentity, domBuilder);
488        
489        return domBuilder.getDocument();
490    }
491    
492    /**
493     * Return the given user
494     * @param email the concerned user's email
495     * @param populationId the concerned user's population id
496     * @return The informations about the given user
497     * @throws SAXException If an error occurred while saxing the user
498     */
499    public static Node userByMail(String email, String populationId) throws SAXException
500    {
501        User user = _userHelper.getUserByEmail(populationId, email);
502        if (user != null)
503        {
504            return user(UserIdentity.userIdentityToString(user.getIdentity()));
505        }
506        return null;
507    }
508    
509    /**
510     * Returns the list of the current user's groups.
511     * @return the list of the current user's groups. Can be null if there is no connected user.
512     */
513    public static NodeList groups()
514    {
515        UserIdentity userIdentity = _currentUserProvider.getUser();
516        return userIdentity != null ? groups(userIdentity.getLogin(), userIdentity.getPopulationId()) : null;
517    }
518    
519    /**
520     * Returns the of the given user's group.
521     * @param userIdentity the concerned user's login + population
522     * @return the of the given user's group.
523     */
524    public static NodeList groups(String userIdentity)
525    {
526        UserIdentity userIdentityObject = UserIdentity.stringToUserIdentity(userIdentity);
527        if (userIdentityObject == null)
528        {
529            return null;
530        }
531        else
532        {
533            return groups(userIdentityObject.getLogin(), userIdentityObject.getPopulationId());
534        }
535    }
536    
537    /**
538     * Returns the of the given user's group.
539     * @param login the concerned user's login.
540     * @param populationId the concerned user's population.
541     * @return the of the given user's group.
542     */
543    public static NodeList groups(String login, String populationId)
544    {
545        ArrayList<Node> groups = new ArrayList<>();
546        
547        Set<GroupIdentity> userGroups = _groupManager.getUserGroups(new UserIdentity(login, populationId));
548        for (GroupIdentity groupId : userGroups)
549        {
550            Group group = _groupManager.getGroup(groupId);
551            if (group != null)
552            {
553                Map<String, String> attributes = new HashMap<>();
554                attributes.put("name", groupId.getId());
555                attributes.put("directory", groupId.getDirectoryId());
556                groups.add(new StringElement("group", attributes, group.getLabel()));
557            }
558        }
559        
560        return new AmetysNodeList(groups);
561    }
562
563    /**
564     * Parse a JSON string as a Map and return the value with the desired key
565     * @param jsonString the JSON representation of the object.
566     * @param key name of the value to return
567     * @return the value as a String, or empty string if an error occurred or the key was not found.
568     */
569    public static String getValueFromJsonObject(String jsonString, String key)
570    {
571        try
572        {
573            Map<String, Object> jsonMap = _jsonUtils.convertJsonToMap(jsonString);
574            if (jsonMap.containsKey(key))
575            {
576                Object value = jsonMap.get(key);
577                if (value instanceof Map || value instanceof Collection)
578                {
579                    _LOGGER.warn("Unable to get string value for key '{}' from json object {}: the value can not be a map nor collection", key, jsonString);
580                }
581                else if (value instanceof Date)
582                {
583                    return DateUtils.dateToString((Date) value);
584                }
585                else
586                {
587                    return value.toString();
588                }
589            }
590        }
591        catch (Exception e)
592        {
593            _LOGGER.warn("Unable to parse json object {}", jsonString, e);
594        }
595        
596        return "";
597    }
598    
599    /**
600     * Determines if the current logged user has right on a String context
601     * @param rightId The id of right
602     * @param objectCtx the context. Can be null to search on any context.
603     * @return true if the current user is allowed, false otherwise
604     */
605    public static boolean hasRight(String rightId, String objectCtx)
606    {
607        return _rightManager.currentUserHasRight(rightId, objectCtx) == RightResult.RIGHT_ALLOW;
608    }
609}