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