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}