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.Comparator; 021import java.util.List; 022import java.util.Set; 023import java.util.stream.Collectors; 024import java.util.stream.Stream; 025 026import javax.jcr.Node; 027import javax.jcr.RepositoryException; 028import javax.jcr.Value; 029 030import org.apache.avalon.framework.component.Component; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034import org.apache.commons.lang3.StringUtils; 035 036import org.ametys.cms.data.ContentValue; 037import org.ametys.cms.repository.Content; 038import org.ametys.cms.repository.ContentTypeExpression; 039import org.ametys.cms.repository.LanguageExpression; 040import org.ametys.core.util.LambdaUtils; 041import org.ametys.plugins.repository.AmetysObjectIterable; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.plugins.repository.AmetysRepositoryException; 044import org.ametys.plugins.repository.UnknownAmetysObjectException; 045import org.ametys.plugins.repository.jcr.JCRAmetysObject; 046import org.ametys.plugins.repository.query.QueryHelper; 047import org.ametys.plugins.repository.query.SortCriteria; 048import org.ametys.plugins.repository.query.expression.AndExpression; 049import org.ametys.plugins.repository.query.expression.Expression; 050import org.ametys.plugins.repository.query.expression.Expression.Operator; 051import org.ametys.plugins.repository.query.expression.MetadataExpression; 052import org.ametys.plugins.repository.query.expression.NotExpression; 053import org.ametys.plugins.repository.query.expression.OrExpression; 054import org.ametys.plugins.repository.query.expression.StringExpression; 055import org.ametys.plugins.repository.query.expression.VirtualFactoryExpression; 056import org.ametys.plugins.userdirectory.page.VirtualOrganisationChartPageFactory; 057import org.ametys.runtime.plugin.component.AbstractLogEnabled; 058import org.ametys.web.repository.page.Page; 059import org.ametys.web.repository.page.PageQueryHelper; 060 061/** 062 * Component providing methods to retrieve organization chart virtual pages, such as the organization chart root and orgUnit page. 063 */ 064public class OrganisationChartPageHandler extends AbstractLogEnabled implements Component, Serviceable 065{ 066 /** The avalon role. */ 067 public static final String ROLE = OrganisationChartPageHandler.class.getName(); 068 069 /** The orgUnit parent attribute name */ 070 public static final String PARENT_ORGUNIT_ATTRIBUTE_NAME = "parentOrgUnit"; 071 072 /** The orgUnit child attribute name */ 073 public static final String CHILD_ORGUNIT_ATTRIBUTE_NAME = "childOrgUnits"; 074 075 /** The attribute name for orgUnit users repeater */ 076 public static final String ORGUNIT_USERS_ATTRIBUTE_NAME = "users"; 077 078 /** The attribute name for orgUnit user in repeater */ 079 public static final String ORGUNIT_USER_ATTRIBUTE_NAME = "user"; 080 081 /** The attribute name for orgUnit user role in repeater */ 082 public static final String ORGUNIT_USER_ROLE_ATTRIBUTE_NAME = "role"; 083 084 /** The data name for the content type of the orgUnit chart */ 085 public static final String CONTENT_TYPE_DATA_NAME = "organisation-chart-root-contenttype"; 086 /** The data name for the child visiblity of the orgUnit chart */ 087 public static final String PAGE_VISIBLE_DATA_NAME = "organization-chart-page-visible"; 088 089 private static final Comparator<Content> __ORGUNIT_COMPARATOR = Comparator 090 // Compare by order data 091 .<Content>comparingLong(c -> c.<Long>getValue("order", false, Long.MAX_VALUE)) 092 // Then by title in lower case 093 .thenComparing(c -> c.getTitle().toLowerCase(), Comparator.naturalOrder()); 094 095 /** The ametys object resolver. */ 096 protected AmetysObjectResolver _resolver; 097 098 @Override 099 public void service(ServiceManager manager) throws ServiceException 100 { 101 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 102 } 103 104 /** 105 * Get orgUnit contents from rootPage 106 * @param rootPage the root page 107 * @return the list of orgUnit contents 108 */ 109 public Stream<Content> getContentsForRootPage(Page rootPage) 110 { 111 String lang = rootPage.getSitemapName(); 112 String contentType = getContentTypeId(rootPage); 113 return getFirstLevelOfContents(lang, contentType); 114 } 115 116 /** 117 * Get orgUnit contents 118 * @param lang the language 119 * @param contentType the content type of organization chart 120 * @return the list of orgUnit contents at the first level 121 */ 122 public Stream<Content> getFirstLevelOfContents(String lang, String contentType) 123 { 124 ContentTypeExpression contentTypeExp = new ContentTypeExpression(Operator.EQ, contentType); 125 MetadataExpression parentMetadataExpression = new MetadataExpression(PARENT_ORGUNIT_ATTRIBUTE_NAME); 126 OrExpression noParentExpression = new OrExpression(new NotExpression(parentMetadataExpression), new StringExpression(PARENT_ORGUNIT_ATTRIBUTE_NAME, Operator.EQ, "")); 127 128 Expression finalExpr = new AndExpression(noParentExpression, contentTypeExp, new LanguageExpression(Operator.EQ, lang)); 129 130 SortCriteria sort = new SortCriteria(); 131 sort.addCriterion(Content.ATTRIBUTE_TITLE, true, true); 132 133 String xPathQuery = QueryHelper.getXPathQuery(null, "ametys:content", finalExpr, sort); 134 135 return _resolver.<Content>query(xPathQuery).stream().sorted(__ORGUNIT_COMPARATOR); 136 } 137 138 /** 139 * True if the page is the organization chart root page 140 * @param jcrPage the page 141 * @return true if the page is the organization chart root page 142 */ 143 public boolean isOrganisationChartRootPage (JCRAmetysObject jcrPage) 144 { 145 try 146 { 147 Node node = jcrPage.getNode(); 148 149 if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY)) 150 { 151 List<Value> values = Arrays.asList(node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues()); 152 153 return values.stream() 154 .map(LambdaUtils.wrap(Value::getString)) 155 .anyMatch(v -> VirtualOrganisationChartPageFactory.class.getName().equals(v)); 156 } 157 else 158 { 159 return false; 160 } 161 } 162 catch (RepositoryException e) 163 { 164 return false; 165 } 166 } 167 168 /** 169 * Get child orgUnit contents from parentContent 170 * @param parentContent the parent content 171 * @return the list of child orgUnit contents 172 */ 173 public List<Content> getChildContents(Content parentContent) 174 { 175 List<Content> contentList = new ArrayList<>(); 176 177 ContentValue[] contents = parentContent.getValue(CHILD_ORGUNIT_ATTRIBUTE_NAME); 178 if (contents != null) 179 { 180 for (ContentValue content : contents) 181 { 182 try 183 { 184 contentList.add(content.getContent()); 185 } 186 catch (UnknownAmetysObjectException e) 187 { 188 getLogger().warn("The parent entity {} ({}) is referencing an unexisting child node '{}'", parentContent.getTitle(), parentContent.getId(), content.getContentId(), e); 189 } 190 } 191 } 192 193 contentList.sort(__ORGUNIT_COMPARATOR); 194 195 return contentList; 196 } 197 198 /** 199 * Get parent orgUnit content from childContent 200 * @param childContent the child content 201 * @return the parent orgUnit content 202 */ 203 public Content getParentContent(Content childContent) 204 { 205 ContentValue content = childContent.getValue(PARENT_ORGUNIT_ATTRIBUTE_NAME); 206 if (content != null) 207 { 208 try 209 { 210 return content.getContent(); 211 } 212 catch (UnknownAmetysObjectException e) 213 { 214 getLogger().warn("There is no parent content with id " + content.getContentId(), e); 215 } 216 } 217 218 return null; 219 } 220 221 /** 222 * Get users contents from content 223 * @param content the content 224 * @return the list of user contents 225 */ 226 public List<Content> getUserContents(Content content) 227 { 228 List<Content> users = new ArrayList<>(); 229 230 ContentValue[] contents = content.getValue(ORGUNIT_USERS_ATTRIBUTE_NAME + "/" + ORGUNIT_USER_ATTRIBUTE_NAME, true); 231 232 if (contents != null) 233 { 234 for (ContentValue contentValue : contents) 235 { 236 try 237 { 238 users.add(contentValue.getContent()); 239 } 240 catch (Exception e) 241 { 242 getLogger().warn("The entity {} ({}) is referencing an unexisting user content node '{}'", content.getTitle(), content.getId(), contentValue.getContentId(), e); 243 } 244 } 245 } 246 247 return users; 248 } 249 250 /** 251 * Get the child content. Return null if it not exist 252 * @param parentContent the parent content 253 * @param path the path from the parent content 254 * @return the child content. 255 */ 256 public Content getChildFromPath(Content parentContent, String path) 257 { 258 String contentName = path.contains("/") ? StringUtils.substringBefore(path, "/") : path; 259 260 List<Content> childContents = getChildContents(parentContent); 261 List<Content> contentFilter = childContents.stream().filter(c -> c.getName().equals(contentName)).collect(Collectors.toList()); 262 263 if (!contentFilter.isEmpty()) 264 { 265 if (path.contains("/")) 266 { 267 return getChildFromPath(contentFilter.get(0), StringUtils.substringAfter(path, "/")); 268 } 269 else 270 { 271 return contentFilter.get(0); 272 } 273 } 274 275 return null; 276 } 277 278 /** 279 * Get the organization chart root page. 280 * @param siteName the current site. 281 * @param sitemapName the sitemap name. 282 * @return the organization chart root page 283 * @throws AmetysRepositoryException if an error occurred. 284 */ 285 public Set<Page> getOrganisationChartRootPages(String siteName, String sitemapName) throws AmetysRepositoryException 286 { 287 Expression expression = new VirtualFactoryExpression(VirtualOrganisationChartPageFactory.class.getName()); 288 289 String query = PageQueryHelper.getPageXPathQuery(siteName, sitemapName, null, expression, null); 290 291 AmetysObjectIterable<Page> pages = _resolver.query(query); 292 293 return pages.stream().collect(Collectors.toSet()); 294 } 295 296 /** 297 * Get the organization chart root page. 298 * @param siteName the current site. 299 * @param sitemapName the sitemap name. 300 * @param contentTypeId The content type id 301 * @return the organization chart root page 302 * @throws AmetysRepositoryException if an error occurred. 303 */ 304 public Page getOrganisationChartRootPage(String siteName, String sitemapName, String contentTypeId) throws AmetysRepositoryException 305 { 306 String contentTypeIdToCompare = contentTypeId != null ? contentTypeId : ""; 307 308 for (Page orgUnitRootPage : getOrganisationChartRootPages(siteName, sitemapName)) 309 { 310 if (contentTypeIdToCompare.equals(getContentTypeId(orgUnitRootPage))) 311 { 312 return orgUnitRootPage; 313 } 314 } 315 316 return null; 317 } 318 319 /** 320 * Gets the content type id 321 * @param rootPage The organisation chart root page 322 * @return the content type id 323 */ 324 public String getContentTypeId(Page rootPage) 325 { 326 return rootPage.getValue(CONTENT_TYPE_DATA_NAME); 327 } 328}