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