001/*
002 *  Copyright 2015 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.web.repository.site;
017
018import java.util.ArrayList;
019import java.util.HashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.stream.Collectors;
023
024import org.apache.avalon.framework.parameters.Parameters;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.cocoon.acting.ServiceableAction;
028import org.apache.cocoon.environment.ObjectModelHelper;
029import org.apache.cocoon.environment.Redirector;
030import org.apache.cocoon.environment.Request;
031import org.apache.cocoon.environment.SourceResolver;
032import org.apache.commons.lang.BooleanUtils;
033import org.apache.commons.lang.StringUtils;
034
035import org.ametys.cms.repository.Content;
036import org.ametys.core.cocoon.JSonReader;
037import org.ametys.core.group.GroupDirectoryContextHelper;
038import org.ametys.core.group.GroupDirectoryDAO;
039import org.ametys.core.right.RightManager;
040import org.ametys.core.right.RightManager.RightResult;
041import org.ametys.core.user.CurrentUserProvider;
042import org.ametys.core.user.UserIdentity;
043import org.ametys.core.user.population.PopulationContextHelper;
044import org.ametys.core.user.population.UserPopulationDAO;
045import org.ametys.plugins.repository.AmetysObject;
046import org.ametys.plugins.repository.AmetysObjectIterable;
047import org.ametys.plugins.repository.AmetysObjectResolver;
048import org.ametys.plugins.repository.query.SortCriteria;
049import org.ametys.plugins.repository.query.expression.Expression;
050import org.ametys.web.filter.SharedContentsHelper;
051import org.ametys.web.site.SiteConfigurationExtensionPoint;
052
053/**
054 * Action for getting the list of existing {@link Site}.
055 */
056public class GetSitesAction extends ServiceableAction
057{
058    /** The site manager */
059    protected SiteManager _siteManager;
060    /** The rights manager */
061    protected RightManager _rightManager;
062    /** The current user provider */
063    protected CurrentUserProvider _userProvider;
064    /** The population context helper */
065    protected PopulationContextHelper _populationContextHelper;
066    /** The group directory context helper */
067    protected GroupDirectoryContextHelper _groupDirectoryContextHelper;
068    /** The user population DAO */
069    protected UserPopulationDAO _userPopulationDAO;
070    /** The group directory DAO */
071    protected GroupDirectoryDAO _groupDirectoryDAO;
072    /** Site type EP */
073    protected SiteTypesExtensionPoint _siteTypeExtensionPoint;
074    /** Site configuration */
075    protected SiteConfigurationExtensionPoint _siteConfiguration;
076    /** Ametys resolver */
077    protected AmetysObjectResolver _ametysResolver;
078    
079    @Override
080    public void service(ServiceManager smanager) throws ServiceException
081    {
082        super.service(smanager);
083        _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE);
084        _rightManager = (RightManager) smanager.lookup(RightManager.ROLE);
085        _userProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE);
086        _siteTypeExtensionPoint = (SiteTypesExtensionPoint) smanager.lookup(SiteTypesExtensionPoint.ROLE);
087        _siteConfiguration = (SiteConfigurationExtensionPoint) smanager.lookup(SiteConfigurationExtensionPoint.ROLE);
088        _ametysResolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
089        _populationContextHelper = (PopulationContextHelper) smanager.lookup(PopulationContextHelper.ROLE);
090        _groupDirectoryContextHelper = (GroupDirectoryContextHelper) smanager.lookup(GroupDirectoryContextHelper.ROLE);
091        _userPopulationDAO = (UserPopulationDAO) smanager.lookup(UserPopulationDAO.ROLE);
092        _groupDirectoryDAO = (GroupDirectoryDAO) smanager.lookup(GroupDirectoryDAO.ROLE);
093    }
094    
095    @Override
096    public Map act(Redirector redirector, SourceResolver resolver, Map objectModel, String source, Parameters parameters) throws Exception
097    {
098        Request request = ObjectModelHelper.getRequest(objectModel);
099        Map<String, Object> result = new HashMap<>();
100        
101        UserIdentity user = _userProvider.getUser();
102        
103        String id = request.getParameter("id");
104        
105        boolean recursive = parameters.getParameterAsBoolean("recursive", BooleanUtils.toBoolean(request.getParameter("recursive")));
106        boolean readAccessOnly = parameters.getParameterAsBoolean("readAccessOnly", BooleanUtils.toBoolean(request.getParameter("readAccessOnly")));
107        boolean sharedSitesOnly = parameters.getParameterAsBoolean("sharedSitesOnly", BooleanUtils.toBoolean(request.getParameter("sharedSitesOnly")));
108        
109        List<Map<String, Object>> sites = new ArrayList<>();
110        
111        try (AmetysObjectIterable<Site> rootSites = _getRootSites(id);)
112        {
113            String currentSiteName = (String) request.getAttribute("siteName");
114            
115            for (Site site : rootSites)
116            {
117                sites.addAll(_rootSite2json(request, site, currentSiteName, recursive, readAccessOnly, sharedSitesOnly, user));
118            }
119        }
120        
121        result.put("sites", sites);
122        
123        request.setAttribute(JSonReader.OBJECT_TO_READ, result);
124        
125        return EMPTY_MAP;
126    }
127    
128    /**
129     * Get root sites
130     * @param id Optionnal root id
131     * @return Ametys iterable of sites
132     */
133    protected AmetysObjectIterable<Site> _getRootSites(String id)
134    {
135        if (StringUtils.isNotEmpty(id))
136        {
137            AmetysObject ao = _ametysResolver.resolveById(id);
138
139            if (ao instanceof Site)
140            {
141                return ((Site) ao).getChildrenSites();
142            }
143        }
144        return _siteManager.getRootSites();
145    }
146    
147    /**
148     * Retrieves the JSON for a root site
149     * @param request The request
150     * @param site The root site
151     * @param currentSiteName The name of the current site
152     * @param recursive True to handle child sites
153     * @param readAccessOnly Only read access site
154     * @param sharedSitesOnly Only shared sites
155     * @param user The current user
156     * @return The root site info
157     */
158    protected List<Map<String, Object>> _rootSite2json(Request request, Site site, String currentSiteName, boolean recursive, boolean readAccessOnly, boolean sharedSitesOnly, UserIdentity user)
159    {
160        List<Map<String, Object>> sites = new ArrayList<>();
161        
162        if (recursive)
163        {
164            // on recursion, returns all sub sites on the same level. The real level is determined by the "depth" property 
165            sites.addAll(_childSites2json(request, site, currentSiteName, 0, readAccessOnly, sharedSitesOnly));
166        }
167        else
168        {
169            sites.add(_site2json(request, site, currentSiteName, 0, readAccessOnly, sharedSitesOnly));
170        }
171        
172        return sites;
173    }
174    
175    /**
176     * Child site to JSON
177     * @param request The request
178     * @param site The root site
179     * @param currentSiteName The name of the current site
180     * @param depth The depth from the root site
181     * @param readAccessOnly Only read access site
182     * @param sharedSitesOnly Only shared sites
183     * @return The child sites info
184     */
185    protected List<Map<String, Object>> _childSites2json (Request request, Site site, String currentSiteName, int depth, boolean readAccessOnly, boolean sharedSitesOnly)
186    {
187        List<Map<String, Object>> childrenAndItseft = new ArrayList<>();
188        
189        childrenAndItseft.add(_site2json(request, site, currentSiteName, depth, readAccessOnly, sharedSitesOnly));
190        
191        // Get child site recursively
192        for (Site child : site.getChildrenSites())
193        {
194            childrenAndItseft.addAll(_childSites2json(request, child, currentSiteName, depth + 1, readAccessOnly, sharedSitesOnly));
195        }
196        
197        return childrenAndItseft;
198    }
199    
200    /**
201     * Site to JSON
202     * @param request The request
203     * @param site The root site
204     * @param currentSiteName The name of the current site
205     * @param depth The depth from the root site
206     * @param readAccessOnly Only read access site
207     * @param sharedSitesOnly Only shared sites
208     * @return The site info
209     */
210    protected Map<String, Object> _site2json (Request request, Site site, String currentSiteName, int depth, boolean readAccessOnly, boolean sharedSitesOnly)
211    {
212        Map<String, Object> object = new HashMap<>();
213        
214        // Calculate shared contents only if necessary
215        long sharedContentsCount = sharedSitesOnly ? _sharedContentsSize(site, currentSiteName) : 0;
216        
217        boolean readAccessCondition = !readAccessOnly || (readAccessOnly && canRead(site, request));
218        boolean sharedSiteCondition = !sharedSitesOnly || (sharedSitesOnly && sharedContentsCount > 0);
219        
220        if (readAccessCondition && sharedSiteCondition)
221        {
222            object = _site2json(site);
223            object.put("depth", depth);
224            object.put("current", site.getName().equals(currentSiteName));
225            object.put("sharedContentsCount", sharedContentsCount);
226            
227            if (site.getChildrenSiteNames().isEmpty())
228            {
229                object.put("sites", new ArrayList<>());
230            }
231        }
232        return object;
233    }
234    
235    /**
236     * Get the JSON representation of a site
237     * @param site The site
238     * @return The site as JSON object
239     */
240    protected Map<String, Object> _site2json (Site site)
241    {
242        Map<String, Object> object = new HashMap<>();
243        
244        object.put("id", site.getId());
245        object.put("name", site.getName());
246        object.put("title", StringUtils.defaultString(site.getTitle()));
247        object.put("description", StringUtils.defaultString(site.getDescription()));
248        object.put("url", StringUtils.defaultString(site.getUrl()));
249        object.put("valid", _siteConfiguration.isValid(site.getName()));
250        object.put("color", site.getColor());
251        
252        SiteType siteType = _siteTypeExtensionPoint.getExtension(site.getType());
253        
254        String iconGlyph = siteType.getIconGlyph();
255        if (iconGlyph != null)
256        {
257            object.put("iconGlyph", iconGlyph);
258        }
259        
260        object.put("iconSmall", siteType.getSmallIcon());
261        object.put("iconLarge", siteType.getLargeIcon());
262        object.put("typeLabel", siteType.getLabel());
263        object.put("type", siteType.getId());
264        object.put("privateType", siteType.isPrivateType());
265        
266        object.put("populations", _populationContextHelper.getUserPopulationsOnContext("/sites/" + site.getName(), true).stream()
267                                                                                                                        .map(_userPopulationDAO::getUserPopulation)
268                                                                                                                        .map(_userPopulationDAO::getUserPopulationAsJson)
269                                                                                                                        .collect(Collectors.toList()));
270        object.put("populationsFO", _populationContextHelper.getUserPopulationsOnContext("/sites-fo/" + site.getName(), true).stream()
271                                                                                                                             .map(_userPopulationDAO::getUserPopulation)
272                                                                                                                             .map(_userPopulationDAO::getUserPopulationAsJson)
273                                                                                                                             .collect(Collectors.toList()));
274        
275        object.put("groups", _groupDirectoryContextHelper.getGroupDirectoriesOnContext("/sites/" + site.getName()).stream()
276                                                                                                                  .map(_groupDirectoryDAO::getGroupDirectory)
277                                                                                                                  .map(_groupDirectoryDAO::getGroupDirectory2Json)
278                                                                                                                  .collect(Collectors.toList()));
279        object.put("groupsFO", _groupDirectoryContextHelper.getGroupDirectoriesOnContext("/sites-fo/" + site.getName()).stream()
280                                                                                                                       .map(_groupDirectoryDAO::getGroupDirectory)
281                                                                                                                       .map(_groupDirectoryDAO::getGroupDirectory2Json)
282                                                                                                                       .collect(Collectors.toList()));
283        
284        return object;
285    }
286    
287    private long _sharedContentsSize (Site site, String currentSiteName)
288    {
289        if (currentSiteName == null)
290        {
291            return 0;
292        }
293        
294        Site currentSite = _siteManager.getSite(currentSiteName);
295        Expression expr = SharedContentsHelper.getSharedContentsExpression(currentSite, site);
296        
297        SortCriteria sortCriteria = new SortCriteria();
298        sortCriteria.addCriterion("title", true, true);
299        String xPathQuery = org.ametys.plugins.repository.query.QueryHelper.getXPathQuery(null, "ametys:content", expr, sortCriteria);
300        
301        AmetysObjectIterable<Content> contents = _ametysResolver.query(xPathQuery);
302        return contents.getSize();
303    }
304    
305    /**
306     * Determines if the current user has a read access on site
307     * @param site the site
308     * @param request the request
309     * @return true if current user has read access
310     */
311    protected boolean canRead (Site site, Request request)
312    {
313        String currentSiteName = (String) request.getAttribute("siteName");
314        
315        try
316        {
317            request.setAttribute("siteName", site.getName());
318            return _rightManager.hasRight(_userProvider.getUser(), "Web_Right_Site_See_Contents", "/cms") == RightResult.RIGHT_ALLOW;
319        }
320        finally
321        {
322            request.setAttribute("siteName", currentSiteName);
323        }
324    }
325    
326}