001/*
002 *  Copyright 2017 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;
017
018import java.util.ArrayList;
019import java.util.Arrays;
020import java.util.List;
021import java.util.stream.Collectors;
022
023import javax.jcr.Node;
024import javax.jcr.RepositoryException;
025import javax.jcr.Value;
026
027import org.apache.avalon.framework.component.Component;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.commons.lang.StringUtils;
032
033import org.ametys.cms.repository.Content;
034import org.ametys.cms.repository.ContentTypeExpression;
035import org.ametys.cms.repository.LanguageExpression;
036import org.ametys.core.util.LambdaUtils;
037import org.ametys.plugins.repository.AmetysObjectIterable;
038import org.ametys.plugins.repository.AmetysObjectResolver;
039import org.ametys.plugins.repository.AmetysRepositoryException;
040import org.ametys.plugins.repository.jcr.JCRAmetysObject;
041import org.ametys.plugins.repository.metadata.CompositeMetadata;
042import org.ametys.plugins.repository.query.QueryHelper;
043import org.ametys.plugins.repository.query.SortCriteria;
044import org.ametys.plugins.repository.query.expression.AndExpression;
045import org.ametys.plugins.repository.query.expression.Expression;
046import org.ametys.plugins.repository.query.expression.Expression.Operator;
047import org.ametys.plugins.repository.query.expression.MetadataExpression;
048import org.ametys.plugins.repository.query.expression.NotExpression;
049import org.ametys.plugins.repository.query.expression.VirtualFactoryExpression;
050import org.ametys.plugins.userdirectory.page.VirtualOrganisationChartPageFactory;
051import org.ametys.runtime.plugin.component.AbstractLogEnabled;
052import org.ametys.web.repository.page.Page;
053import org.ametys.web.repository.page.PageQueryHelper;
054
055/**
056 * Component providing methods to retrieve organization chart virtual pages, such as the organization chart root and orgUnit page.
057 */
058public class OrganisationChartPageHandler extends AbstractLogEnabled implements Component, Serviceable
059{
060    /** The avalon role. */
061    public static final String ROLE = OrganisationChartPageHandler.class.getName();
062    
063    /** The orgUnit content type */
064    public static final String ORGUNIT_CONTENT_TYPE = "org.ametys.plugins.userdirectory.Content.udorgunit";
065    
066    /** The orgUnit parent attribute name */
067    public static final String PARENT_ORGUNIT_ATTRIBUTE_NAME = "parentOrgUnit";
068    
069    /** The orgUnit child attribute name */
070    public static final String CHILD_ORGUNIT_ATTRIBUTE_NAME = "childOrgUnits";
071    
072    /** The attribute name for orgUnit users repeater */
073    public static final String ORGUNIT_USERS_ATTRIBUTE_NAME = "users";
074    
075    /** The attribute name for orgUnit user in repeater */
076    public static final String ORGUNIT_USER_ATTRIBUTE_NAME = "user";
077    
078    /** The attribute name for orgUnit user role in repeater */
079    public static final String ORGUNIT_USER_ROLE_ATTRIBUTE_NAME = "role";
080    
081    /** The ametys object resolver. */
082    protected AmetysObjectResolver _resolver;
083    
084    @Override
085    public void service(ServiceManager manager) throws ServiceException
086    {
087        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
088    }
089    
090    /**
091     * Get orgUnit contents from rootPage
092     * @param rootPage the root page
093     * @return the list of orgUnit contents
094     */
095    public AmetysObjectIterable<Content> getContentsForRootPage(Page rootPage)
096    {
097        String lang = rootPage.getSitemapName();
098        return getFirstLevelOfContents(lang);
099    }
100    
101    /**
102     * Get orgUnit contents
103     * @param lang the language
104     * @return the list of orgUnit contents at the first level
105     */
106    public AmetysObjectIterable<Content> getFirstLevelOfContents(String lang)
107    {
108        ContentTypeExpression contentTypeExp = new ContentTypeExpression(Operator.EQ, ORGUNIT_CONTENT_TYPE);
109        MetadataExpression parentMetadataExpression = new MetadataExpression(PARENT_ORGUNIT_ATTRIBUTE_NAME);
110        NotExpression noParentExpression = new NotExpression(parentMetadataExpression);
111        
112        Expression finalExpr = new AndExpression(noParentExpression, contentTypeExp, new LanguageExpression(Operator.EQ, lang));
113        
114        SortCriteria sort = new SortCriteria();
115        sort.addCriterion(Content.ATTRIBUTE_TITLE, true, true);
116        
117        String xPathQuery = QueryHelper.getXPathQuery(null, "ametys:content", finalExpr, sort);
118        
119        return _resolver.query(xPathQuery);
120    }
121    
122    /**
123     * True if the page is the organization chart root page
124     * @param jcrPage the page
125     * @return true if the page is the organization chart root page
126     */
127    public boolean isOrganisationChartRootPage (JCRAmetysObject jcrPage)
128    {
129        try
130        {
131            Node node = jcrPage.getNode();
132            
133            if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
134            {
135                List<Value> values = Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues());
136                
137                return values.stream()
138                        .map(LambdaUtils.wrap(Value::getString))
139                        .anyMatch(v -> VirtualOrganisationChartPageFactory.class.getName().equals(v));
140            }
141            else
142            {
143                return false;
144            }
145        }
146        catch (RepositoryException e)
147        {
148            return false;
149        }
150    }
151    
152    /**
153     * Get child orgUnit contents from parentContent
154     * @param parentContent the parent content
155     * @return the list of child orgUnit contents
156     */
157    public List<Content> getChildContents(Content parentContent)
158    {
159        List<Content> contentList = new ArrayList<>();
160        String[] contents = parentContent.getMetadataHolder().getStringArray(CHILD_ORGUNIT_ATTRIBUTE_NAME, new String[0]);
161        for (String contentId : contents)
162        {
163            try
164            {
165                Content content = _resolver.resolveById(contentId);
166                contentList.add(content);
167            }
168            catch (Exception e)
169            {
170                getLogger().warn("The parent entity {} ({}) is referencing an unexisting child node '{}'", parentContent.getTitle(), parentContent.getId(), contentId, e);
171            }
172        }
173        
174        contentList.sort((c1, c2) -> c1.getTitle().toLowerCase().compareTo(c2.getTitle().toLowerCase()));
175        return contentList;
176    }
177    
178    /**
179     * Get parent orgUnit content from childContent
180     * @param childContent the child content
181     * @return the parent orgUnit content
182     */
183    public Content getParentContent(Content childContent)
184    {
185        String contentId = childContent.getMetadataHolder().getString(PARENT_ORGUNIT_ATTRIBUTE_NAME, null);
186        if (contentId != null)
187        {
188            return _resolver.resolveById(contentId);
189        }
190        
191        return null;
192    }
193    
194    /**
195     * Get users contents from content
196     * @param content the content
197     * @return the list of user contents
198     */
199    public List<Content> getUserContents(Content content)
200    {
201        List<Content> users = new ArrayList<>();
202        CompositeMetadata metaHolder = content.getMetadataHolder();
203        if (metaHolder.hasMetadata(ORGUNIT_USERS_ATTRIBUTE_NAME))
204        {
205            CompositeMetadata repeater = metaHolder.getCompositeMetadata(ORGUNIT_USERS_ATTRIBUTE_NAME);
206            for (String name : repeater.getMetadataNames())
207            {
208                CompositeMetadata entry = repeater.getCompositeMetadata(name);
209                String userId = entry.getString(ORGUNIT_USER_ATTRIBUTE_NAME, null);
210                if (StringUtils.isNotBlank(userId))
211                {
212                    try
213                    {
214                        Content user = _resolver.resolveById(userId);
215                        users.add(user);
216                    }
217                    catch (Exception e)
218                    {
219                        getLogger().warn("The entity {} ({}) is referencing an unexisting user content node '{}'", content.getTitle(), content.getId(), userId, e);
220                    }
221                }
222            }
223        }
224
225        return users;
226    }
227    
228    /**
229     * Get the child content. Return null if it not exist
230     * @param parentContent the parent content
231     * @param path the path from the parent content
232     * @return the child content.
233     */
234    public Content getChildFromPath(Content parentContent, String path)
235    {
236        String contentName = path.contains("/") ? StringUtils.substringBefore(path, "/") : path;
237        
238        List<Content> childContents = getChildContents(parentContent);
239        List<Content> contentFilter = childContents.stream().filter(c -> c.getName().equals(contentName)).collect(Collectors.toList());
240        
241        if (!contentFilter.isEmpty())
242        {
243            if (path.contains("/"))
244            {
245                return getChildFromPath(contentFilter.get(0), StringUtils.substringAfter(path, "/"));
246            }
247            else
248            {
249                return contentFilter.get(0);
250            }
251        }
252        
253        return null;
254    }
255    
256    /**
257     * Get the organization chart root page.
258     * @param siteName the current site.
259     * @param sitemapName the sitemap name.
260     * @return the organization chart root page
261     * @throws AmetysRepositoryException if an error occurred.
262     */
263    public Page getOrganisationChartRootPages(String siteName, String sitemapName) throws AmetysRepositoryException
264    {
265        Expression expression = new VirtualFactoryExpression(VirtualOrganisationChartPageFactory.class.getName());
266        
267        String query = PageQueryHelper.getPageXPathQuery(siteName, sitemapName, null, expression, null);
268        
269        AmetysObjectIterable<Page> pages = _resolver.query(query);
270        
271        return pages.iterator().hasNext() ? pages.iterator().next() : null;
272    }
273}