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