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