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