001/*
002 *  Copyright 2018 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.linkdirectory;
017
018import java.util.Collection;
019import java.util.List;
020import java.util.Map;
021import java.util.stream.Collectors;
022
023import org.apache.avalon.framework.context.Context;
024import org.apache.avalon.framework.context.ContextException;
025import org.apache.avalon.framework.context.Contextualizable;
026import org.apache.avalon.framework.logger.AbstractLogEnabled;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.avalon.framework.service.Serviceable;
030import org.apache.cocoon.ProcessingException;
031import org.apache.cocoon.components.ContextHelper;
032import org.apache.cocoon.environment.Request;
033import org.apache.cocoon.xml.AttributesImpl;
034import org.apache.cocoon.xml.XMLUtils;
035import org.apache.commons.lang3.StringUtils;
036import org.xml.sax.ContentHandler;
037import org.xml.sax.SAXException;
038
039import org.ametys.core.user.CurrentUserProvider;
040import org.ametys.core.user.UserIdentity;
041import org.ametys.plugins.linkdirectory.LinkDirectoryThemesInputDataHelper.ConfiguredThemesInputData;
042import org.ametys.plugins.linkdirectory.LinkDirectoryThemesInputDataHelper.ThemesInputData;
043import org.ametys.plugins.linkdirectory.repository.DefaultLink;
044import org.ametys.web.inputdata.InputData;
045import org.ametys.web.repository.page.Page;
046import org.ametys.web.repository.site.Site;
047
048/**
049 * Input data for the link directory user preferences in thumbnails mode 
050 */
051public class LinkDirectoryInputData extends AbstractLogEnabled implements Contextualizable, InputData, Serviceable
052{
053    /** The current user provider */
054    protected CurrentUserProvider _currentUserProvider;
055    
056    /** The Avalon context */
057    private Context _context;
058    
059    private DirectoryHelper _directoryHelper;
060    
061    /** The link directory themes input data helper */
062    protected LinkDirectoryThemesInputDataHelper _linkDirectoryThemesInputDataHelper;
063    
064    @Override
065    public void contextualize(Context context) throws ContextException
066    {
067        _context = context; 
068    }
069    
070    @Override
071    public void service(ServiceManager manager) throws ServiceException
072    {
073        _linkDirectoryThemesInputDataHelper = (LinkDirectoryThemesInputDataHelper) manager.lookup(LinkDirectoryThemesInputDataHelper.ROLE);
074        _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE);
075        _directoryHelper = (DirectoryHelper) manager.lookup(DirectoryHelper.ROLE);
076    }
077    
078    @Override
079    public boolean isCacheable(Site site, Page currentPage)
080    {
081        Request request = ContextHelper.getRequest(_context);
082        
083        String template = _getTemplate(request);
084        if (template == null)
085        {
086            return true;
087        }
088        
089        try
090        {
091            ConfiguredThemesInputData configuredThemesInputData = _linkDirectoryThemesInputDataHelper.getThemesInputData(site.getSkinId());
092            List<ThemesInputData> themesInputData = _getThemesForSkinAndTemplate(configuredThemesInputData, template);
093            if (themesInputData.isEmpty())
094            {
095                // The current template is not configured for a link directory input data
096                return true;
097            }
098            
099            for (ThemesInputData themeInputData : themesInputData)
100            {
101                if (themeInputData.configurable() || themeInputData.displayUserLinks())
102                {
103                    // The applications are configurable
104                    return false;
105                }
106            }
107            
108            // Find the configured theme ids for this template
109            String language = _directoryHelper.getLanguage(request);
110            List<String> configuredThemesNames = themesInputData.stream()
111                .map(inputData -> _getConfiguredThemes(inputData, language))
112                .flatMap(Collection::stream)
113                .collect(Collectors.toList());
114            String siteName = _directoryHelper.getSiteName(request);
115            
116            return !_directoryHelper.hasRestrictions(siteName, language, configuredThemesNames) && !_directoryHelper.hasInternalUrl(siteName, language, configuredThemesNames);
117        }
118        catch (Exception e)
119        {
120            getLogger().error("An error occurred while retrieving information from the skin configuration", e);
121            // Configuration file is not readable => toSAX method will not generate any xml
122            return true;
123        }
124    }
125
126    @Override
127    public void toSAX(ContentHandler contentHandler) throws ProcessingException
128    {
129        Request request = ContextHelper.getRequest(_context);
130        
131        // Get the current user's login if he is in the front office
132        UserIdentity user = _currentUserProvider.getUser();
133
134        String template = _getTemplate(request);
135        if (template == null)
136        {
137            getLogger().info("There is no current template");
138            return; 
139        }
140        
141        String skinId = _getSkin(request);
142        if (skinId == null)
143        {
144            getLogger().info("There is no current skin");
145            return; 
146        }
147        
148        try
149        {
150            contentHandler.startDocument();
151
152            ConfiguredThemesInputData configuredThemesInputData = _linkDirectoryThemesInputDataHelper.getThemesInputData(skinId);
153            List<ThemesInputData> themesInputDatas = _getThemesForSkinAndTemplate(configuredThemesInputData, template);
154            
155            // Is there an error in the configuration file ?
156            String error = configuredThemesInputData.error();
157            if (StringUtils.isNotBlank(error))
158            {
159                AttributesImpl attrs = new AttributesImpl();
160                attrs.addCDATAAttribute("error", error);
161                XMLUtils.createElement(contentHandler, "linkDirectory", attrs);
162            }
163            else
164            {
165                String language = _directoryHelper.getLanguage(request);
166                String siteName = _directoryHelper.getSiteName(request);
167                for (ThemesInputData themesInputData : themesInputDatas)
168                {
169                    AttributesImpl attrs = new AttributesImpl();
170                    attrs.addCDATAAttribute("applicable", Boolean.TRUE.toString());
171                    attrs.addCDATAAttribute("configurable", String.valueOf(themesInputData.configurable()));
172                    attrs.addCDATAAttribute("displayUserLinks", String.valueOf(themesInputData.displayUserLinks()));
173                    attrs.addCDATAAttribute("id", themesInputData.id());
174                    
175                    XMLUtils.startElement(contentHandler, "linkDirectory", attrs);
176                    
177                    List<String> configuredThemesNames = _getConfiguredThemes(themesInputData, language);
178                    if (configuredThemesNames != null)
179                    {
180                        Map<String, List<String>> themesMap = _directoryHelper.getThemesMap(configuredThemesNames, siteName, language);
181                        List<String> correctThemesIds = themesMap.get("themes");
182                        List<String> unknownThemesNames = themesMap.get("unknown-themes");
183
184                        _saxThemes(contentHandler, correctThemesIds, unknownThemesNames);
185                        _saxLinks(contentHandler, user, request, correctThemesIds, themesInputData.displayUserLinks(), themesInputData.configurable(), themesInputData.id());
186                    }
187                    
188                    XMLUtils.endElement(contentHandler, "linkDirectory");
189                }
190            }
191        }
192        catch (Exception e)
193        {
194            getLogger().error("An exception occurred during the processing of the link directory's input data" , e);
195        }
196    }
197    
198    private void _saxThemes(ContentHandler contentHandler, List<String> themeIds, List<String> unknownThemesNames) throws SAXException
199    {
200        if (!themeIds.isEmpty())
201        {
202            XMLUtils.startElement(contentHandler, "themes");
203            for (String themeId : themeIds)
204            {
205                XMLUtils.createElement(contentHandler, "theme", themeId);
206            }
207            XMLUtils.endElement(contentHandler, "themes");
208        }
209        
210        if (!unknownThemesNames.isEmpty())
211        {
212            AttributesImpl attr = new AttributesImpl();
213            attr.addCDATAAttribute("count", Integer.toString(unknownThemesNames.size()));
214            XMLUtils.createElement(contentHandler, "unknown-themes", attr, StringUtils.join(unknownThemesNames, ", "));
215        }
216    }
217
218    private void _saxLinks(ContentHandler contentHandler, UserIdentity user, Request request, List<String> themeIds, boolean displayUserLinks, boolean configurable, String specificContext) throws ProcessingException
219    {
220        String language = _directoryHelper.getLanguage(request);
221        String siteName = _directoryHelper.getSiteName(request);
222        try
223        {
224            // SAX common links
225            List<DefaultLink> links = _directoryHelper.getLinks(themeIds, siteName, language);
226            
227            List<DefaultLink> userLinks = null;
228            if (user != null && displayUserLinks)
229            {
230                userLinks = _directoryHelper.getUserLinks(siteName, language, user).stream().collect(Collectors.toList());
231            }
232            
233            
234            // SAX the user own links
235            XMLUtils.startElement(contentHandler, "links");
236            
237            try
238            {
239                String storageContext = siteName + "/" + language;
240                if (StringUtils.isNotEmpty(specificContext))
241                {
242                    storageContext += "/" + specificContext;
243                }
244                _directoryHelper.saxLinks(siteName, contentHandler, links, userLinks, themeIds, configurable, _directoryHelper.getContextVars(request), storageContext, user);
245            }
246            catch (Exception e)
247            {
248                getLogger().error("An exception occurred while saxing the links", e);
249            }
250            
251            XMLUtils.endElement(contentHandler, "links");
252        }
253        catch (Exception e)
254        {
255            throw new ProcessingException("An error occurred while retrieving or saxing the links", e);
256        }
257    }
258
259    /**
260     * Retrieve the configured themes names defined in the skin file link-directory.xml for the current input data and the current language
261     * @param themesInputData Can be an empty {@link String}
262     * @param lang language to filter by. Themes with lang=null will always be returned.
263     * @return the list of configured themes ids, can be empty, cannot be null
264     */
265    private List<String> _getConfiguredThemes(ThemesInputData themesInputData, String lang)
266    {
267        return themesInputData.themes()
268            .stream()
269            .filter(t -> t.get("lang") == null || t.get("lang").equals(lang))
270            .map(t -> t.get("id"))
271            .collect(Collectors.toList());
272    }
273    
274    private List<ThemesInputData> _getThemesForSkinAndTemplate(ConfiguredThemesInputData configuredThemesInputData, String template) 
275    {
276        List<ThemesInputData> themesInputDatas = configuredThemesInputData.themesInputDatas();
277        if (themesInputDatas == null)
278        {
279            return List.of();
280        }
281        
282        return themesInputDatas.stream()
283            .filter(t -> _filterByTemplate(t, template))
284            .collect(Collectors.toList());
285    }
286    
287    private boolean _filterByTemplate(ThemesInputData theme, String template)
288    {
289        List<String> templates = theme.templates();
290        return templates.contains(template) || templates.contains(LinkDirectoryThemesInputDataHelper.WILDCARD); 
291    }
292       
293    /**
294     * Get the current template 
295     * @param request the request
296     * @return the current template
297     */
298    private String _getTemplate(Request request)
299    {
300        return (String) request.getAttribute("template");
301    }
302    
303     /**
304      * Get the current skin 
305      * @param request the request
306      * @return the current skin
307      */
308    private String _getSkin(Request request)
309    {
310        return (String) request.getAttribute("skin");
311    }
312}