001/*
002 *  Copyright 2016 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.plugins.userdirectory.page;
017
018import java.util.HashSet;
019import java.util.Iterator;
020import java.util.Set;
021
022import org.apache.avalon.framework.activity.Initializable;
023import org.apache.avalon.framework.component.Component;
024import org.apache.avalon.framework.context.Context;
025import org.apache.avalon.framework.context.ContextException;
026import org.apache.avalon.framework.context.Contextualizable;
027import org.apache.avalon.framework.logger.AbstractLogEnabled;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.cocoon.components.ContextHelper;
032import org.apache.cocoon.environment.Request;
033import org.apache.commons.lang.StringUtils;
034
035import org.ametys.cms.languages.LanguagesManager;
036import org.ametys.cms.repository.Content;
037import org.ametys.core.cache.AbstractCacheManager;
038import org.ametys.core.cache.Cache;
039import org.ametys.plugins.core.impl.cache.AbstractCacheKey;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.plugins.repository.RepositoryConstants;
042import org.ametys.plugins.repository.UnknownAmetysObjectException;
043import org.ametys.plugins.repository.provider.RequestAttributeWorkspaceSelector;
044import org.ametys.plugins.userdirectory.UserDirectoryPageHandler;
045import org.ametys.runtime.i18n.I18nizableText;
046import org.ametys.web.repository.page.Page;
047
048/**
049 * Resolves an user directory page path from the associated user content.
050 */
051public class UserDirectoryPageResolver extends AbstractLogEnabled implements Component, Serviceable, Initializable, Contextualizable
052{
053    /** The avalon role. */
054    public static final String ROLE = UserDirectoryPageResolver.class.getName();
055    
056    /** The key cache for empty site */
057    protected static final String _CACHE_KEY_EMPTY_SITE = "_ametys_no_site";
058    
059    /** The ametys object resolver. */
060    protected AmetysObjectResolver _ametysResolver;
061    /** The user directory page handler */
062    protected UserDirectoryPageHandler _pageHandler;
063    /** The cache manager */
064    protected AbstractCacheManager _cacheManager;
065    /** The context */
066    protected Context _context;
067    /** The languages manager */
068    protected LanguagesManager _languagesManager;
069    
070    static class UserPageElementKey extends AbstractCacheKey
071    {
072        UserPageElementKey(String lang, String contentId, String siteName, String contentTypeId)
073        {
074            super(lang, contentId, siteName, contentTypeId);
075        }
076        
077        static UserPageElementKey of(String lang, String contentId, String siteName, String contentTypeId)
078        {
079            return new UserPageElementKey(lang, contentId, siteName, contentTypeId);
080        }
081        
082        static UserPageElementKey of(String lang)
083        {
084            return new UserPageElementKey(lang, null, null, null);
085        } 
086    }
087    
088    @Override
089    public void service(ServiceManager serviceManager) throws ServiceException
090    {
091        _ametysResolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
092        _pageHandler = (UserDirectoryPageHandler) serviceManager.lookup(UserDirectoryPageHandler.ROLE);
093        _cacheManager = (AbstractCacheManager) serviceManager.lookup(AbstractCacheManager.ROLE);
094        _languagesManager = (LanguagesManager) serviceManager.lookup(LanguagesManager.ROLE);
095    }
096    
097    @Override
098    public void initialize() throws Exception
099    {
100        _cacheManager.createMemoryCache(ROLE, 
101                new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_USER_PAGE_CACHE_LABEL"),
102                new I18nizableText("plugin.user-directory", "PLUGINS_USER_DIRECTORY_USER_PAGE_CACHE_DESCRIPTION"),
103                true,
104                null);
105    }
106    
107    public void contextualize(Context context) throws ContextException
108    {
109        _context = context;
110    }
111    
112    /**
113     * Return the user page
114     * @param userContent the user page content
115     * @param siteName The current site name.
116     * @param sitemapName The current sitemap name.
117     * @return the user page or <code>null</code> if the user page does not exist
118     */
119    public UserPage getUserPage(Content userContent, String siteName, String sitemapName)
120    {
121        for (String contentTypeId : userContent.getTypes())
122        {
123            UserPage userPage = getUserPage(userContent, siteName, sitemapName, contentTypeId);
124            if (userPage != null)
125            {
126                return userPage;
127            }
128        }
129        
130        return null;
131    }
132    
133    /**
134     * Return the user page
135     * @param userContent the user page content
136     * @param siteName The current site name.
137     * @param sitemapName The current sitemap name.
138     * @param contentTypeId the content type id
139     * @return the user page or <code>null</code> if the user page does not exist
140     */
141    public UserPage getUserPage(Content userContent, String siteName, String sitemapName, String contentTypeId)
142    {
143        String siteKey = StringUtils.isNotBlank(siteName) ? siteName : _CACHE_KEY_EMPTY_SITE;
144        String pageId = null;
145        String contentLang = userContent.getLanguage();
146        if (StringUtils.isNotBlank(contentLang))
147        {
148            // Just get the user page of content language
149            UserPageElementKey key = UserPageElementKey.of(contentLang, userContent.getId(), siteKey, contentTypeId);
150            pageId = _getCache().get(key, k -> _getUserPageId(userContent, siteName, contentLang, contentTypeId));
151        }
152        else
153        {
154            // The content is multilingual, so test different languages starting with the sitemap name to get an user content page
155            Iterator<String> languageIterator = _getLanguagesToTest(sitemapName).iterator();
156            while (pageId == null && languageIterator.hasNext())
157            {
158                String lang = languageIterator.next();
159                UserPageElementKey key = UserPageElementKey.of(lang, userContent.getId(), siteKey, contentTypeId);
160                pageId = _getCache().get(key, k -> _getUserPageId(userContent, siteName, lang, contentTypeId));
161            }
162        }
163        
164        return _getUserPage(pageId);
165    }
166    
167    private Set<String> _getLanguagesToTest(String givenLang)
168    {
169        Set<String> languages = new HashSet<>();
170        
171        // First, test the given language if not blank
172        if (StringUtils.isNotBlank(givenLang))
173        {
174            languages.add(givenLang);
175        }
176        
177        // Then test english language
178        languages.add("en");
179        
180        // Finaly test other languages
181        languages.addAll(_languagesManager.getAvailableLanguages().keySet());
182        
183        return languages;
184    }
185    
186    /**
187     * Return the user page id
188     * @param userContent the user content
189     * @param siteName the site name
190     * @param language the given language
191     * @param contentTypeId the content type id
192     * @return the user page id or <code>null</code> if the user page does not exist
193     */
194    protected String _getUserPageId(Content userContent, String siteName, String language, String contentTypeId)
195    {
196        Request request = ContextHelper.getRequest(_context);
197        
198        // Retrieve current workspace
199        String currentWsp = RequestAttributeWorkspaceSelector.getForcedWorkspace(request);
200        
201        try
202        {
203            // Use default workspace
204            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, RepositoryConstants.DEFAULT_WORKSPACE);
205            
206            Page userDirectoryRootPage = _pageHandler.getUserDirectoryRootPage(siteName, language, contentTypeId);
207            if (userDirectoryRootPage == null)
208            {
209                return null;
210            }
211            
212            return getUserPageId(userDirectoryRootPage, userContent);
213        }
214        finally
215        {
216            // Restore context
217            RequestAttributeWorkspaceSelector.setForcedWorkspace(request, currentWsp);
218        }
219    }
220    
221    /**
222     * Return the user page
223     * @param userDirectoryRootPage the user directory root page
224     * @param userContent the user content
225     * @return the user page or <code>null</code> if the user page does not exist
226     */
227    public UserPage getUserPage(Page userDirectoryRootPage, Content userContent)
228    {
229        String pageId = getUserPageId(userDirectoryRootPage, userContent);
230        return _getUserPage(pageId);
231    }
232    
233    /**
234     * Return the user page identifier
235     * @param userDirectoryRootPage the user directory root page
236     * @param userContent the user content
237     * @return the user page or <code>null</code> if the user page does not exist
238     */
239    public String getUserPageId(Page userDirectoryRootPage, Content userContent)
240    {
241        // E.g: uduser://path?rootId=...&contentId=...
242        String userPath = _getUserPath(userDirectoryRootPage, userContent);
243        if (userPath != null)
244        {
245            return UserPage.getId(userPath, userDirectoryRootPage.getId(), userContent.getId());
246        }
247        return null;
248    }
249    
250    /**
251     * Get the path of a user content
252     * @param userDirectoryRootPage The user directory root page where to look into
253     * @param userContent The user content
254     * @return The path or <code>null</code> if the user page does not exist
255     */
256    private String _getUserPath(Page userDirectoryRootPage, Content userContent)
257    {
258        String value = _pageHandler.getTransformedClassificationValue(userDirectoryRootPage, userContent);
259        return value != null ? value.replaceAll("(.)(?!$)", "$1/") : null;
260    }
261    
262    private UserPage _getUserPage(String pageId)
263    {
264        try
265        {
266            return pageId != null ? _ametysResolver.resolveById(pageId) : null;
267        }
268        catch (UnknownAmetysObjectException e)
269        {
270            return null;
271        }
272    }
273    
274    private Cache<UserPageElementKey, String> _getCache()
275    {
276        return _cacheManager.get(ROLE);
277    }
278    
279    /**
280     * Invalidate the user page cache for one language. If lang is <code>null</code>, invalidate all
281     * @param lang the lang. Can be <code>null</code>
282     */
283    public void invalidateUserPageCache(String lang)
284    {
285        if (StringUtils.isNotBlank(lang)) 
286        {
287            _getCache().invalidate(UserPageElementKey.of(lang));
288        }
289        else 
290        {
291            _getCache().invalidateAll();
292        }
293    }
294}