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.List; 021import java.util.stream.Collectors; 022 023import javax.jcr.Node; 024import javax.jcr.RepositoryException; 025import javax.jcr.Value; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.commons.lang.StringUtils; 032 033import org.ametys.cms.repository.Content; 034import org.ametys.cms.repository.ContentTypeExpression; 035import org.ametys.cms.repository.LanguageExpression; 036import org.ametys.core.util.LambdaUtils; 037import org.ametys.plugins.repository.AmetysObjectIterable; 038import org.ametys.plugins.repository.AmetysObjectResolver; 039import org.ametys.plugins.repository.AmetysRepositoryException; 040import org.ametys.plugins.repository.UnknownAmetysObjectException; 041import org.ametys.plugins.repository.jcr.JCRAmetysObject; 042import org.ametys.plugins.repository.metadata.CompositeMetadata; 043import org.ametys.plugins.repository.query.QueryHelper; 044import org.ametys.plugins.repository.query.SortCriteria; 045import org.ametys.plugins.repository.query.expression.AndExpression; 046import org.ametys.plugins.repository.query.expression.Expression; 047import org.ametys.plugins.repository.query.expression.Expression.Operator; 048import org.ametys.plugins.repository.query.expression.MetadataExpression; 049import org.ametys.plugins.repository.query.expression.NotExpression; 050import org.ametys.plugins.repository.query.expression.VirtualFactoryExpression; 051import org.ametys.plugins.userdirectory.page.VirtualOrganisationChartPageFactory; 052import org.ametys.runtime.plugin.component.AbstractLogEnabled; 053import org.ametys.web.repository.page.Page; 054import org.ametys.web.repository.page.PageQueryHelper; 055 056/** 057 * Component providing methods to retrieve organization chart virtual pages, such as the organization chart root and orgUnit page. 058 */ 059public class OrganisationChartPageHandler extends AbstractLogEnabled implements Component, Serviceable 060{ 061 /** The avalon role. */ 062 public static final String ROLE = OrganisationChartPageHandler.class.getName(); 063 064 /** The orgUnit content type */ 065 public static final String ORGUNIT_CONTENT_TYPE = "org.ametys.plugins.userdirectory.Content.udorgunit"; 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, ORGUNIT_CONTENT_TYPE); 110 MetadataExpression parentMetadataExpression = new MetadataExpression(PARENT_ORGUNIT_ATTRIBUTE_NAME); 111 NotExpression noParentExpression = new NotExpression(parentMetadataExpression); 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 String[] contents = parentContent.getMetadataHolder().getStringArray(CHILD_ORGUNIT_ATTRIBUTE_NAME, new String[0]); 162 for (String contentId : contents) 163 { 164 try 165 { 166 Content content = _resolver.resolveById(contentId); 167 contentList.add(content); 168 } 169 catch (Exception e) 170 { 171 getLogger().warn("The parent entity {} ({}) is referencing an unexisting child node '{}'", parentContent.getTitle(), parentContent.getId(), contentId, e); 172 } 173 } 174 175 contentList.sort((c1, c2) -> c1.getTitle().toLowerCase().compareTo(c2.getTitle().toLowerCase())); 176 return contentList; 177 } 178 179 /** 180 * Get parent orgUnit content from childContent 181 * @param childContent the child content 182 * @return the parent orgUnit content 183 */ 184 public Content getParentContent(Content childContent) 185 { 186 String contentId = childContent.getMetadataHolder().getString(PARENT_ORGUNIT_ATTRIBUTE_NAME, null); 187 if (contentId != null) 188 { 189 try 190 { 191 return _resolver.resolveById(contentId); 192 } 193 catch (UnknownAmetysObjectException e) 194 { 195 getLogger().warn("There is no parent content with id " + contentId, e); 196 } 197 } 198 199 return null; 200 } 201 202 /** 203 * Get users contents from content 204 * @param content the content 205 * @return the list of user contents 206 */ 207 public List<Content> getUserContents(Content content) 208 { 209 List<Content> users = new ArrayList<>(); 210 CompositeMetadata metaHolder = content.getMetadataHolder(); 211 if (metaHolder.hasMetadata(ORGUNIT_USERS_ATTRIBUTE_NAME)) 212 { 213 CompositeMetadata repeater = metaHolder.getCompositeMetadata(ORGUNIT_USERS_ATTRIBUTE_NAME); 214 for (String name : repeater.getMetadataNames()) 215 { 216 CompositeMetadata entry = repeater.getCompositeMetadata(name); 217 String userId = entry.getString(ORGUNIT_USER_ATTRIBUTE_NAME, null); 218 if (StringUtils.isNotBlank(userId)) 219 { 220 try 221 { 222 Content user = _resolver.resolveById(userId); 223 users.add(user); 224 } 225 catch (Exception e) 226 { 227 getLogger().warn("The entity {} ({}) is referencing an unexisting user content node '{}'", content.getTitle(), content.getId(), userId, e); 228 } 229 } 230 } 231 } 232 233 return users; 234 } 235 236 /** 237 * Get the child content. Return null if it not exist 238 * @param parentContent the parent content 239 * @param path the path from the parent content 240 * @return the child content. 241 */ 242 public Content getChildFromPath(Content parentContent, String path) 243 { 244 String contentName = path.contains("/") ? StringUtils.substringBefore(path, "/") : path; 245 246 List<Content> childContents = getChildContents(parentContent); 247 List<Content> contentFilter = childContents.stream().filter(c -> c.getName().equals(contentName)).collect(Collectors.toList()); 248 249 if (!contentFilter.isEmpty()) 250 { 251 if (path.contains("/")) 252 { 253 return getChildFromPath(contentFilter.get(0), StringUtils.substringAfter(path, "/")); 254 } 255 else 256 { 257 return contentFilter.get(0); 258 } 259 } 260 261 return null; 262 } 263 264 /** 265 * Get the organization chart root page. 266 * @param siteName the current site. 267 * @param sitemapName the sitemap name. 268 * @return the organization chart root page 269 * @throws AmetysRepositoryException if an error occurred. 270 */ 271 public Page getOrganisationChartRootPages(String siteName, String sitemapName) throws AmetysRepositoryException 272 { 273 Expression expression = new VirtualFactoryExpression(VirtualOrganisationChartPageFactory.class.getName()); 274 275 String query = PageQueryHelper.getPageXPathQuery(siteName, sitemapName, null, expression, null); 276 277 AmetysObjectIterable<Page> pages = _resolver.query(query); 278 279 return pages.iterator().hasNext() ? pages.iterator().next() : null; 280 } 281}