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