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