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.page; 017 018import java.util.ArrayList; 019import java.util.Collections; 020import java.util.List; 021import java.util.Locale; 022import java.util.Map; 023import java.util.Set; 024import java.util.stream.Collectors; 025 026import javax.jcr.Node; 027import javax.jcr.RepositoryException; 028import javax.jcr.Value; 029 030import org.apache.commons.lang.StringUtils; 031 032import org.ametys.cms.repository.Content; 033import org.ametys.cms.repository.DefaultContent; 034import org.ametys.core.group.GroupIdentity; 035import org.ametys.core.right.RightManager; 036import org.ametys.core.user.UserIdentity; 037import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollection; 038import org.ametys.plugins.contentio.synchronize.SynchronizableContentsCollectionDAO; 039import org.ametys.plugins.explorer.resources.ResourceCollection; 040import org.ametys.plugins.repository.ACLAmetysObject; 041import org.ametys.plugins.repository.AmetysObject; 042import org.ametys.plugins.repository.AmetysObjectIterable; 043import org.ametys.plugins.repository.AmetysObjectResolver; 044import org.ametys.plugins.repository.AmetysRepositoryException; 045import org.ametys.plugins.repository.CollectionIterable; 046import org.ametys.plugins.repository.UnknownAmetysObjectException; 047import org.ametys.plugins.repository.data.holder.ModelLessDataHolder; 048import org.ametys.plugins.repository.data.holder.impl.DefaultModelLessDataHolder; 049import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 050import org.ametys.plugins.repository.data.repositorydata.impl.MemoryRepositoryData; 051import org.ametys.plugins.repository.data.type.RepositoryModelItemType; 052import org.ametys.plugins.repository.metadata.CompositeMetadata; 053import org.ametys.plugins.userdirectory.OrganisationChartPageHandler; 054import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint; 055import org.ametys.web.repository.page.Page; 056import org.ametys.web.repository.page.UnknownZoneException; 057import org.ametys.web.repository.page.Zone; 058import org.ametys.web.repository.site.Site; 059import org.ametys.web.repository.sitemap.Sitemap; 060import org.ametys.web.skin.Skin; 061import org.ametys.web.skin.SkinsManager; 062 063/** 064 * Page representing an orgUnit page. 065 */ 066public class OrgUnitPage implements Page, ACLAmetysObject 067{ 068 private static final String __ORGUNIT_PAGE_TEMPLATE = "udorgunit"; 069 070 private Page _root; 071 private String _title; 072 private AmetysObjectResolver _resolver; 073 private OrganisationChartPageHandler _organisationChartPageHandler; 074 private SynchronizableContentsCollectionDAO _syncContentsCollectionDAO; 075 private SkinsManager _skinsManager; 076 private String _path; 077 private Content _syncContent; 078 079 private AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _pageDataTypeExtensionPoint; 080 private AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _zoneDataTypeExtensionPoint; 081 private AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _zoneItemDataTypeExtensionPoint; 082 083 /** 084 * Constructor. 085 * @param root the root page. 086 * @param syncContent the synchronized content 087 * @param path the path 088 * @param resolver the {@link AmetysObjectResolver}. 089 * @param organisationChartPageHandler the organisation chart page handler 090 * @param syncContentsCollectionDAO The DAO for synchronizable collections 091 * @param skinsManager the skins manager 092 * @param pageDataTypeExtensionPoint the extension point with available data types for pages 093 * @param zoneDataTypeExtensionPoint the extension point with available data types for zones 094 * @param zoneItemDataTypeExtensionPoint the extension point with available data types for zone items 095 */ 096 public OrgUnitPage(Page root, Content syncContent, String path, AmetysObjectResolver resolver, OrganisationChartPageHandler organisationChartPageHandler, SynchronizableContentsCollectionDAO syncContentsCollectionDAO, SkinsManager skinsManager, AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> pageDataTypeExtensionPoint, AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> zoneDataTypeExtensionPoint, AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> zoneItemDataTypeExtensionPoint) 097 { 098 _root = root; 099 _path = path; 100 _resolver = resolver; 101 _organisationChartPageHandler = organisationChartPageHandler; 102 _syncContentsCollectionDAO = syncContentsCollectionDAO; 103 _skinsManager = skinsManager; 104 _syncContent = syncContent; 105 106 _title = _syncContent.getTitle(new Locale(root.getSitemapName())); 107 108 _pageDataTypeExtensionPoint = pageDataTypeExtensionPoint; 109 _zoneDataTypeExtensionPoint = zoneDataTypeExtensionPoint; 110 _zoneItemDataTypeExtensionPoint = zoneItemDataTypeExtensionPoint; 111 } 112 113 /** 114 * Returns the associated synchronizable {@link Content}. 115 * @return the associated synchronizable {@link Content}. 116 */ 117 public Content getSyncContent() 118 { 119 return _syncContent; 120 } 121 122 @Override 123 public int getDepth() throws AmetysRepositoryException 124 { 125 return _root.getDepth() + (_path != null ? _path.split("/").length : 0); 126 } 127 128 @Override 129 public Set<String> getReferers() throws AmetysRepositoryException 130 { 131 return null; 132 } 133 134 @Override 135 public ResourceCollection getRootAttachments() throws AmetysRepositoryException 136 { 137 return null; 138 } 139 140 @Override 141 public String getTemplate() throws AmetysRepositoryException 142 { 143 Skin skin = _skinsManager.getSkin(getSite().getSkinId()); 144 145 if (skin.getTemplate(__ORGUNIT_PAGE_TEMPLATE) != null) 146 { 147 return __ORGUNIT_PAGE_TEMPLATE; 148 } 149 150 return "page"; 151 } 152 153 @Override 154 public String getTitle() throws AmetysRepositoryException 155 { 156 return _title; 157 } 158 159 @Override 160 public String getLongTitle() throws AmetysRepositoryException 161 { 162 return _title; 163 } 164 165 @Override 166 public PageType getType() throws AmetysRepositoryException 167 { 168 return PageType.CONTAINER; 169 } 170 171 @Override 172 public String getURL() throws AmetysRepositoryException 173 { 174 throw new UnsupportedOperationException("#getURL is not supported on virtual orgUnit pages"); 175 } 176 177 @Override 178 public LinkType getURLType() throws AmetysRepositoryException 179 { 180 throw new UnsupportedOperationException("#getURLType is not supported on virtual orgUnit pages"); 181 } 182 183 @Override 184 public Zone getZone(String name) throws UnknownZoneException, AmetysRepositoryException 185 { 186 if (!"default".equals(name)) 187 { 188 throw new IllegalArgumentException("Only the zone named 'default' is actually supported on virtual orgUnit pages."); 189 } 190 191 return new OrgUnitZone(this, _zoneDataTypeExtensionPoint, _zoneItemDataTypeExtensionPoint); 192 } 193 194 @Override 195 public AmetysObjectIterable< ? extends Zone> getZones() throws AmetysRepositoryException 196 { 197 ArrayList<Zone> zones = new ArrayList<>(); 198 zones.add(new OrgUnitZone(this, _zoneDataTypeExtensionPoint, _zoneItemDataTypeExtensionPoint)); 199 return new CollectionIterable<>(zones); 200 } 201 202 @Override 203 public boolean hasZone(String name) throws AmetysRepositoryException 204 { 205 return "default".equals(name); 206 } 207 208 @Override 209 public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException 210 { 211 ArrayList<Page> children = new ArrayList<>(); 212 213 for (Content content : _organisationChartPageHandler.getChildContents(_syncContent)) 214 { 215 children.add(new OrgUnitPage(_root, content, _path + "/" + content.getName() , _resolver, _organisationChartPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _zoneItemDataTypeExtensionPoint)); 216 } 217 218 return new CollectionIterable<>(children); 219 } 220 221 @Override 222 public String getPathInSitemap() throws AmetysRepositoryException 223 { 224 return _root.getPathInSitemap() + "/" + _path; 225 } 226 227 @Override 228 public Site getSite() throws AmetysRepositoryException 229 { 230 return _root.getSite(); 231 } 232 233 @Override 234 public String getSiteName() throws AmetysRepositoryException 235 { 236 return _root.getSiteName(); 237 } 238 239 @Override 240 public Sitemap getSitemap() throws AmetysRepositoryException 241 { 242 return _root.getSitemap(); 243 } 244 245 @Override 246 public String getSitemapName() throws AmetysRepositoryException 247 { 248 return _root.getSitemapName(); 249 } 250 251 @SuppressWarnings("unchecked") 252 @Override 253 public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException 254 { 255 if (path.isEmpty()) 256 { 257 throw new AmetysRepositoryException("path must be non empty"); 258 } 259 260 Content childContent = _organisationChartPageHandler.getChildFromPath(_syncContent, path); 261 if (childContent != null) 262 { 263 OrgUnitPage page = new OrgUnitPage(_root, childContent, _path + "/" + path , _resolver, _organisationChartPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _zoneItemDataTypeExtensionPoint); 264 return (A) page; 265 } 266 267 return null; 268 } 269 270 271 @SuppressWarnings("unchecked") 272 @Override 273 public AmetysObjectIterable<? extends AmetysObject> getChildren() throws AmetysRepositoryException 274 { 275 return getChildrenPages(); 276 } 277 278 @Override 279 public boolean hasChild(String name) throws AmetysRepositoryException 280 { 281 return !_organisationChartPageHandler.getChildContents(_syncContent).stream().filter(c -> c.getName().equals(name)).collect(Collectors.toList()).isEmpty(); 282 } 283 284 @Override 285 public String getId() throws AmetysRepositoryException 286 { 287 // E.g: udorgunit://path?rootId=...&contentId=... 288 return "udorgunit://" + _path + "?rootId=" + _root.getId() + "&contentId=" + _syncContent.getId(); 289 } 290 291 @Override 292 public String getName() throws AmetysRepositoryException 293 { 294 return _syncContent.getName(); 295 } 296 297 @SuppressWarnings("unchecked") 298 @Override 299 public Page getParent() throws AmetysRepositoryException 300 { 301 Content parentContent = _organisationChartPageHandler.getParentContent(_syncContent); 302 if (parentContent != null) 303 { 304 return new OrgUnitPage(_root, parentContent, StringUtils.substringBeforeLast(_path, "/"), _resolver, _organisationChartPageHandler, _syncContentsCollectionDAO, _skinsManager, _pageDataTypeExtensionPoint, _zoneDataTypeExtensionPoint, _zoneItemDataTypeExtensionPoint); 305 } 306 307 return _root; 308 } 309 310 @Override 311 public String getParentPath() throws AmetysRepositoryException 312 { 313 if (_path.contains("/")) 314 { 315 return _root.getPath() + "/" + StringUtils.substringBeforeLast(_path, "/"); 316 } 317 else 318 { 319 return _root.getPath(); 320 } 321 } 322 323 @Override 324 public String getPath() throws AmetysRepositoryException 325 { 326 return _root.getPath() + "/" + _path; 327 } 328 329 public ModelLessDataHolder getDataHolder() 330 { 331 RepositoryData repositoryData = new MemoryRepositoryData(getName()); 332 return new DefaultModelLessDataHolder(_pageDataTypeExtensionPoint, repositoryData); 333 } 334 335 @Override 336 public Set<String> getTags() throws AmetysRepositoryException 337 { 338 return Collections.emptySet(); 339 } 340 341 @Override 342 public AmetysObjectIterable< ? extends Page> getChildrenPages(boolean includeInvisiblePage) throws AmetysRepositoryException 343 { 344 return getChildrenPages(); 345 } 346 347 @Override 348 public boolean isVisible() throws AmetysRepositoryException 349 { 350 return true; 351 } 352 353 @Override 354 public Page getChildPageAt(int index) throws UnknownAmetysObjectException, AmetysRepositoryException 355 { 356 throw new UnknownAmetysObjectException("There is no child for orgUnit page"); 357 } 358 359 @Override 360 public Set<String> getAllowedProfilesForAnyConnectedUser() 361 { 362 return Collections.EMPTY_SET; 363 } 364 365 @Override 366 public Set<String> getDeniedProfilesForAnyConnectedUser() 367 { 368 return Collections.EMPTY_SET; 369 } 370 371 @Override 372 public Set<String> getAllowedProfilesForAnonymous() 373 { 374 return Collections.EMPTY_SET; 375 } 376 377 @Override 378 public Set<String> getDeniedProfilesForAnonymous() 379 { 380 List<String> collectionIds = _getCollectionIds(); 381 for (String collectionId : collectionIds) 382 { 383 SynchronizableContentsCollection collection = _syncContentsCollectionDAO.getSynchronizableContentsCollection(collectionId); 384 String restrictedField = collection.getRestrictedField(); 385 if (!StringUtils.isEmpty(restrictedField) && _isContentRestricted(restrictedField)) 386 { 387 return Collections.singleton(RightManager.READER_PROFILE_ID); 388 } 389 } 390 return Collections.EMPTY_SET; 391 } 392 393 @Override 394 public Set<String> getAllowedProfilesForUser(UserIdentity user) 395 { 396 return Collections.EMPTY_SET; 397 } 398 399 @Override 400 public Map<UserIdentity, Set<String>> getAllowedProfilesForUsers() 401 { 402 return Collections.EMPTY_MAP; 403 } 404 405 @Override 406 public Set<UserIdentity> getAllowedUsers(String profileId) 407 { 408 return Collections.EMPTY_SET; 409 } 410 411 @Override 412 public Map<GroupIdentity, Set<String>> getAllowedProfilesForGroups() 413 { 414 return Collections.EMPTY_MAP; 415 } 416 417 @Override 418 public Set<GroupIdentity> getAllowedGroups(String profileId) 419 { 420 return Collections.EMPTY_SET; 421 } 422 423 @Override 424 public Set<String> getDeniedProfilesForUser(UserIdentity user) 425 { 426 return Collections.EMPTY_SET; 427 } 428 429 @Override 430 public Map<UserIdentity, Set<String>> getDeniedProfilesForUsers() 431 { 432 return Collections.EMPTY_MAP; 433 } 434 435 @Override 436 public Set<UserIdentity> getDeniedUsers(String profileId) 437 { 438 return Collections.EMPTY_SET; 439 } 440 441 @Override 442 public Map<GroupIdentity, Set<String>> getDeniedProfilesForGroups() 443 { 444 return Collections.EMPTY_MAP; 445 } 446 447 @Override 448 public Set<GroupIdentity> getDeniedGroups(String profileId) 449 { 450 return Collections.EMPTY_SET; 451 } 452 453 private boolean _isContentRestricted (String metadataPath) 454 { 455 CompositeMetadata metadataHolder = _syncContent.getMetadataHolder(); 456 457 String[] pathSegments = metadataPath.split("/"); 458 459 for (int i = 0; i < pathSegments.length - 1; i++) 460 { 461 if (!metadataHolder.hasMetadata(pathSegments[i])) 462 { 463 return false; 464 } 465 metadataHolder = metadataHolder.getCompositeMetadata(pathSegments[i]); 466 } 467 468 String metadataName = pathSegments[pathSegments.length - 1]; 469 return metadataHolder.getBoolean(metadataName, false); 470 } 471 472 private List<String> _getCollectionIds() throws AmetysRepositoryException 473 { 474 List<String> collectionIds = new ArrayList<>(); 475 476 if (_syncContent instanceof DefaultContent) 477 { 478 try 479 { 480 Node node = ((DefaultContent) _syncContent).getNode(); 481 if (node.hasProperty(SynchronizableContentsCollection.COLLECTION_ID_PROPERTY)) 482 { 483 Value[] values = node.getProperty(SynchronizableContentsCollection.COLLECTION_ID_PROPERTY).getValues(); 484 for (Value value : values) 485 { 486 collectionIds.add(value.getString()); 487 } 488 } 489 } 490 catch (RepositoryException e) 491 { 492 throw new AmetysRepositoryException("Failed to get linked synchronizable collections for content " + _syncContent.getId(), e); 493 } 494 } 495 496 return collectionIds; 497 } 498}