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