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     * Split the text.
330     * @param textToSplit the text to split.
331     * @param tokenizers the tokenizer characters.
332     * @param startIndex the minimum number of characters of the result string
333     * @return the split text.
334     */
335    public static String splitText(String textToSplit, String tokenizers, int startIndex)
336    {
337        String tokenizableText = textToSplit.substring(startIndex != 0 ? startIndex - 1 : 0, textToSplit.length());
338        
339        int tokenPlace = StringUtils.indexOfAny(tokenizableText, tokenizers);
340        
341        if (tokenPlace == -1)
342        {
343            return textToSplit;
344        }
345        else
346        {
347            return textToSplit.substring(0, startIndex - 1 + tokenPlace);
348        }
349    }
350    
351    /**
352     * Split the text.
353     * @param textToSplit the text to split.
354     * @param tokenizers the tokenizer characters.
355     * @param maxCharacters the maximum number of characters of the result string
356     * @param currentCharactersNumber the current character number.
357     * @return the split text.
358     */
359    @Deprecated
360    public static String splitText(String textToSplit, String tokenizers, int maxCharacters, int currentCharactersNumber)
361    {
362        int tokenStartIndex = maxCharacters - currentCharactersNumber - 1;
363        String tokenizableText = textToSplit.substring(tokenStartIndex, textToSplit.length());
364        
365        int tokenPlace = StringUtils.indexOfAny(tokenizableText, tokenizers);
366        
367        if (tokenPlace == -1)
368        {
369            return textToSplit;
370        }
371        else
372        {
373            return textToSplit.substring(0, tokenStartIndex + tokenPlace);
374        }
375    }
376    
377    /**
378     * Get the versions of the application.
379     * Default VersionsHandler impl will return Ametys and Application versions
380     * @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)
381     */
382    public static Node versions()
383    {
384        Map<String, Object> versionsMap = new HashMap<>();
385
386        List<Object> versionList = new ArrayList<>();
387
388        for (Version version : _versionHandler.getVersions())
389        {
390            Map<String, Object> versionMap = new HashMap<>();
391
392            String componentName = version.getName();
393            String componentVersion = version.getVersion();
394            String componentDate =  DateUtils.dateToString(version.getDate());
395            
396            if (StringUtils.isNotEmpty(componentName))
397            {
398                versionMap.put("Name", componentName);
399            }
400            if (StringUtils.isNotEmpty(componentVersion))
401            {
402                versionMap.put("Version", componentVersion);
403            }
404            if (StringUtils.isNotEmpty(componentDate))
405            {
406                versionMap.put("Date", componentDate);
407            }
408            
409            versionList.add(versionMap);
410        }
411        
412        versionsMap.put("Component", versionList);
413
414        return new MapElement("Versions", versionsMap);
415    }
416    
417    /**
418     * Get the current mode of the application for the current user.
419     * @return True if the application is in developer mode, false if in production mode.
420     */
421    public static boolean isDeveloperMode()
422    {
423        Request request = ContextHelper.getRequest(_context);
424        
425        DEVMODE developerMode = DevMode.getDeveloperMode(request);
426        return developerMode == DEVMODE.DEVELOPMENT 
427                || developerMode == DEVMODE.SUPER_DEVELOPPMENT;
428    }
429    
430    /**
431     * Return the user
432     * @return The current connected user object or null
433     * @throws SAXException if a problem occured while getting the user
434     */
435    public static Node user() throws SAXException
436    {
437        UserIdentity userIdentity = _currentUserProvider.getUser();
438        if (userIdentity != null)
439        {
440            return user(userIdentity.getLogin(), userIdentity.getPopulationId());
441        }
442        
443        return null;
444    }
445    
446    /**
447     * Return the given user
448     * @param userIdentity the concerned user's login + population
449     * @return The informations about the given user
450     * @throws SAXException If an error occurred while saxing the user
451     */
452    public static Node user(String userIdentity) throws SAXException
453    {
454        UserIdentity userIdentityObject = UserIdentity.stringToUserIdentity(userIdentity);
455        if (userIdentityObject == null)
456        {
457            return null;
458        }
459        else
460        {
461            return user(userIdentityObject.getLogin(), userIdentityObject.getPopulationId());
462        }
463    }
464    
465    /**
466     * Return the given user
467     * @param login the concerned user's login
468     * @param populationId the concerned user's population id
469     * @return The informations about the given user
470     * @throws SAXException If an error occurred while saxing the user
471     */
472    public static Node user(String login, String populationId) throws SAXException
473    {
474        DOMBuilder domBuilder = new DOMBuilder();
475        
476        UserIdentity userIdentity = new UserIdentity(login, populationId);
477        _userHelper.saxUserIdentity(userIdentity, domBuilder);
478        
479        return domBuilder.getDocument();
480    }
481    
482    /**
483     * Return the given user
484     * @param email the concerned user's email
485     * @param populationId the concerned user's population id
486     * @return The informations about the given user
487     * @throws SAXException If an error occurred while saxing the user
488     */
489    public static Node userByMail(String email, String populationId) throws SAXException
490    {
491        User user = _userHelper.getUserByEmail(populationId, email);
492        if (user != null)
493        {
494            return user(UserIdentity.userIdentityToString(user.getIdentity()));
495        }
496        return null;
497    }
498    
499    /**
500     * Returns the list of the current user's groups.
501     * @return the list of the current user's groups. Can be null if there is no connected user.
502     */
503    public static NodeList groups()
504    {
505        UserIdentity userIdentity = _currentUserProvider.getUser();
506        return userIdentity != null ? groups(userIdentity.getLogin(), userIdentity.getPopulationId()) : null;
507    }
508    
509    /**
510     * Returns the of the given user's group.
511     * @param userIdentity the concerned user's login + population
512     * @return the of the given user's group.
513     */
514    public static NodeList groups(String userIdentity)
515    {
516        UserIdentity userIdentityObject = UserIdentity.stringToUserIdentity(userIdentity);
517        if (userIdentityObject == null)
518        {
519            return null;
520        }
521        else
522        {
523            return groups(userIdentityObject.getLogin(), userIdentityObject.getPopulationId());
524        }
525    }
526    
527    /**
528     * Returns the of the given user's group.
529     * @param login the concerned user's login.
530     * @param populationId the concerned user's population.
531     * @return the of the given user's group.
532     */
533    public static NodeList groups(String login, String populationId)
534    {
535        ArrayList<Node> groups = new ArrayList<>();
536        
537        Set<GroupIdentity> userGroups = _groupManager.getUserGroups(new UserIdentity(login, populationId));
538        for (GroupIdentity groupId : userGroups)
539        {
540            Group group = _groupManager.getGroup(groupId);
541            if (group != null)
542            {
543                Map<String, String> attributes = new HashMap<>();
544                attributes.put("name", groupId.getId());
545                attributes.put("directory", groupId.getDirectoryId());
546                groups.add(new StringElement("group", attributes, group.getLabel()));
547            }
548        }
549        
550        return new AmetysNodeList(groups);
551    }
552
553    /**
554     * Parse a JSON string as a Map and return the value with the desired key
555     * @param jsonString the JSON representation of the object.
556     * @param key name of the value to return
557     * @return the value as a String, or empty string if an error occurred or the key was not found.
558     */
559    public static String getValueFromJsonObject(String jsonString, String key)
560    {
561        try
562        {
563            Map<String, Object> jsonMap = _jsonUtils.convertJsonToMap(jsonString);
564            if (jsonMap.containsKey(key))
565            {
566                Object value = jsonMap.get(key);
567                if (value instanceof Map || value instanceof Collection)
568                {
569                    _LOGGER.warn("Unable to get string value for key '{}' from json object {}: the value can not be a map nor collection", key, jsonString);
570                }
571                else if (value instanceof Date)
572                {
573                    return DateUtils.dateToString((Date) value);
574                }
575                else
576                {
577                    return value.toString();
578                }
579            }
580        }
581        catch (Exception e)
582        {
583            _LOGGER.warn("Unable to parse json object {}", jsonString, e);
584        }
585        
586        return "";
587    }
588    
589    /**
590     * Determines if the current logged user has right on a String context
591     * @param rightId The id of right
592     * @param objectCtx the context. Can be null to search on any context.
593     * @return true if the current user is allowed, false otherwise
594     */
595    public static boolean hasRight(String rightId, String objectCtx)
596    {
597        return _rightManager.currentUserHasRight(rightId, objectCtx) == RightResult.RIGHT_ALLOW;
598    }
599}