001/* 002 * Copyright 2010 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.web.repository.site; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.HashMap; 021import java.util.HashSet; 022import java.util.Iterator; 023import java.util.Map; 024import java.util.Set; 025 026import javax.jcr.Repository; 027import javax.jcr.RepositoryException; 028import javax.jcr.Session; 029import javax.jcr.observation.Event; 030import javax.jcr.observation.ObservationManager; 031 032import org.apache.avalon.framework.component.Component; 033import org.apache.avalon.framework.context.Context; 034import org.apache.avalon.framework.context.ContextException; 035import org.apache.avalon.framework.context.Contextualizable; 036import org.apache.avalon.framework.logger.AbstractLogEnabled; 037import org.apache.avalon.framework.service.ServiceException; 038import org.apache.avalon.framework.service.ServiceManager; 039import org.apache.avalon.framework.service.Serviceable; 040import org.apache.cocoon.components.ContextHelper; 041import org.apache.cocoon.environment.Request; 042 043import org.ametys.core.right.RightManager; 044import org.ametys.core.user.CurrentUserProvider; 045import org.ametys.core.user.UserIdentity; 046import org.ametys.core.user.population.PopulationContextHelper; 047import org.ametys.plugins.repository.AmetysObject; 048import org.ametys.plugins.repository.AmetysObjectIterable; 049import org.ametys.plugins.repository.AmetysObjectResolver; 050import org.ametys.plugins.repository.AmetysRepositoryException; 051import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 052import org.ametys.plugins.repository.RepositoryConstants; 053import org.ametys.plugins.repository.RepositoryIntegrityViolationException; 054import org.ametys.plugins.repository.TraversableAmetysObject; 055import org.ametys.plugins.repository.UnknownAmetysObjectException; 056import org.ametys.plugins.repository.provider.AdminSessionProvider; 057import org.ametys.plugins.repository.provider.WorkspaceSelector; 058import org.ametys.web.live.LiveWorkspaceListener; 059import org.ametys.web.repository.page.CopySiteComponent; 060import org.ametys.web.synchronization.SynchronizeComponent; 061 062/** 063 * Helper component for managing sites. 064 */ 065public class SiteManager extends AbstractLogEnabled implements Serviceable, Component, Contextualizable 066{ 067 /** Avalon Role */ 068 public static final String ROLE = SiteManager.class.getName(); 069 070 /** Constant for storing the sites cache in request attribute. */ 071 public static final String SITES_KEY = SiteManager.class.getName(); 072 073 /** sites root's JCR node name */ 074 public static final String ROOT_SITES = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sites"; 075 076 /** sites root's JCR path */ 077 public static final String ROOT_SITES_PATH = "/" + ROOT_SITES; 078 079 private AmetysObjectResolver _resolver; 080 private Repository _repository; 081 private AdminSessionProvider _adminSessionProvider; 082 private SynchronizeComponent _synchronizeComponent; 083 private CopySiteComponent _copySiteComponent; 084 private WorkspaceSelector _workspaceSelector; 085 private RightManager _rightManager; 086 private CurrentUserProvider _currentUserProvider; 087 private PopulationContextHelper _populationContextHelper; 088 089 private Context _context; 090 091 092 093 @Override 094 public void service(ServiceManager manager) throws ServiceException 095 { 096 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 097 _repository = (Repository) manager.lookup("javax.jcr.Repository"); 098 _adminSessionProvider = (AdminSessionProvider) manager.lookup(AdminSessionProvider.ROLE); 099 _synchronizeComponent = (SynchronizeComponent) manager.lookup(SynchronizeComponent.ROLE); 100 _copySiteComponent = (CopySiteComponent) manager.lookup(CopySiteComponent.ROLE); 101 _workspaceSelector = (WorkspaceSelector) manager.lookup(WorkspaceSelector.ROLE); 102 _rightManager = (RightManager) manager.lookup(RightManager.ROLE); 103 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 104 _populationContextHelper = (PopulationContextHelper) manager.lookup(PopulationContextHelper.ROLE); 105 } 106 107 public void contextualize(Context context) throws ContextException 108 { 109 _context = context; 110 } 111 112 private Request _getRequest () 113 { 114 try 115 { 116 return (Request) _context.get(ContextHelper.CONTEXT_REQUEST_OBJECT); 117 } 118 catch (ContextException ce) 119 { 120 getLogger().info("Unable to get the request", ce); 121 return null; 122 } 123 } 124 125 /** 126 * Returns the sites names. 127 * @return the sites names. 128 */ 129 public Collection<String> getRootSiteNames() 130 { 131 TraversableAmetysObject root = _resolver.resolveByPath(ROOT_SITES_PATH); 132 AmetysObjectIterable<AmetysObject> sites = root.getChildren(); 133 134 ArrayList<String> result = new ArrayList<>(); 135 136 for (AmetysObject object : sites) 137 { 138 result.add(object.getName()); 139 } 140 141 return result; 142 } 143 144 /** 145 * Returns the sites names. 146 * @return the sites names. 147 */ 148 public Collection<String> getSiteNames() 149 { 150 ArrayList<String> result = new ArrayList<>(); 151 AmetysObjectIterable<Site> sites = getSites(); 152 153 for (Site site : sites) 154 { 155 result.add(site.getName()); 156 } 157 158 return result; 159 } 160 161 /** 162 * Get the granted site names for the current user 163 * @return The name of sites the current user can access 164 */ 165 public Set<String> getGrantedSites() 166 { 167 return getGrantedSites(_currentUserProvider.getUser()); 168 } 169 170 /** 171 * Get the granted site names for user 172 * @param user The user 173 * @return The name of sites the user can access 174 */ 175 public Set<String> getGrantedSites(UserIdentity user) 176 { 177 Set<String> grantedSiteNames = new HashSet<>(); 178 179 Request request = _getRequest(); 180 181 for (String siteName : getSiteNames()) 182 { 183 if (_populationContextHelper.getUserPopulationsOnContext("/sites/" + siteName, false).contains(user.getPopulationId())) 184 { 185 try 186 { 187 request.setAttribute("siteName", siteName); // Setting temporarily this attribute to check user rights on any object on this site 188 if (!_rightManager.getUserRights(user, "/cms").isEmpty()) 189 { 190 grantedSiteNames.add(siteName); 191 } 192 } 193 finally 194 { 195 request.setAttribute("siteName", null); 196 } 197 } 198 } 199 200 return grantedSiteNames; 201 } 202 203 /** 204 * Determines if the user has granted access to the site 205 * @param user The user 206 * @param siteName The site name 207 * @return <code>true</code> if the user can access to the site 208 */ 209 public boolean isGrantedSite (UserIdentity user, String siteName) 210 { 211 Request request = _getRequest(); 212 try 213 { 214 request.setAttribute("siteName", siteName); // Setting temporarily this attribute to check user rights on any object on this site 215 return !_rightManager.getUserRights(user, "/cms").isEmpty(); 216 } 217 finally 218 { 219 request.setAttribute("siteName", null); 220 } 221 } 222 223 /** 224 * Returns the root for sites. 225 * @return the root for sites. 226 */ 227 public ModifiableTraversableAmetysObject getRoot() 228 { 229 return _resolver.resolveByPath(ROOT_SITES_PATH); 230 } 231 232 /** 233 * Returns the root sites. 234 * @return the root sites. 235 */ 236 public AmetysObjectIterable<Site> getRootSites() 237 { 238 TraversableAmetysObject root = _resolver.resolveByPath(ROOT_SITES_PATH); 239 return root.getChildren(); 240 } 241 242 /** 243 * Returns the all sites. 244 * @return the all sites. 245 */ 246 public AmetysObjectIterable<Site> getSites() 247 { 248 String jcrQuery = "//element(*, ametys:site)"; 249 return _resolver.query(jcrQuery); 250 } 251 252 /** 253 * Creates a site with the given name. 254 * @param siteName the site name. 255 * @param parentId the id of the parent site. Can be null. 256 * @return the newly created {@link Site}. 257 * @throws RepositoryIntegrityViolationException if the named site already exists. 258 */ 259 public Site createSite(String siteName, String parentId) throws RepositoryIntegrityViolationException 260 { 261 ModifiableTraversableAmetysObject root = null; 262 if (parentId != null) 263 { 264 Site site = _resolver.resolveById(parentId); 265 if (!site.hasChild(ROOT_SITES)) 266 { 267 site.createChild(ROOT_SITES, "ametys:sites"); 268 } 269 root = site.getChild(ROOT_SITES); 270 } 271 else 272 { 273 root = _resolver.resolveByPath(ROOT_SITES_PATH); 274 } 275 276 Site site = (Site) root.createChild(siteName, "ametys:site"); 277 try 278 { 279 String sitePath = site.getNode().getPath(); 280 281 // Create the resource explorer root. 282 site.getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources", "ametys:resources-collection"); 283 284 Session session = _adminSessionProvider.getAdminSession(); 285 ObservationManager manager = session.getWorkspace().getObservationManager(); 286 287 manager.addEventListener(new LiveWorkspaceListener(_repository, _synchronizeComponent, getLogger()), 288 Event.NODE_ADDED 289 | Event.NODE_REMOVED 290 | Event.NODE_MOVED 291 | Event.PROPERTY_ADDED 292 | Event.PROPERTY_CHANGED 293 | Event.PROPERTY_REMOVED, 294 sitePath + "/ametys-internal:plugins", true, null, null, false); 295 296 manager.addEventListener(new LiveWorkspaceListener(_repository, _synchronizeComponent, getLogger()), 297 Event.NODE_ADDED 298 | Event.NODE_REMOVED 299 | Event.NODE_MOVED 300 | Event.PROPERTY_ADDED 301 | Event.PROPERTY_CHANGED 302 | Event.PROPERTY_REMOVED, 303 sitePath + "/ametys-internal:resources", true, null, null, false); 304 } 305 catch (RepositoryException ex) 306 { 307 throw new AmetysRepositoryException(ex); 308 } 309 310 return site; 311 } 312 313 /** 314 * Creates a site with the given name. 315 * @param siteName the site name. 316 * @return the newly created {@link Site}. 317 * @throws RepositoryIntegrityViolationException if the named site already exists. 318 */ 319 public Site createSite(String siteName) throws RepositoryIntegrityViolationException 320 { 321 return createSite (siteName, null); 322 } 323 324 /** 325 * Creates a site with the given name, from another site to copy 326 * @param site the site to be copied 327 * @param siteName the site name 328 * @return the newly created {@link Site}. 329 * @throws RepositoryIntegrityViolationException if the named site already exists. 330 */ 331 public Site copySite(Site site, String siteName) throws RepositoryIntegrityViolationException 332 { 333 return copySite(site, null, siteName); 334 } 335 336 /** 337 * Creates a site with the given name, from another site to copy 338 * @param site the site to be copied 339 * @param parentId the id of the parent site. Can be null. 340 * @param siteName the site name 341 * @return the newly created {@link Site}. 342 * @throws RepositoryIntegrityViolationException if the named site already exists. 343 */ 344 public Site copySite(Site site, String parentId, String siteName) throws RepositoryIntegrityViolationException 345 { 346 ModifiableTraversableAmetysObject root = null; 347 if (parentId != null) 348 { 349 root = _resolver.resolveById(parentId); 350 } 351 else 352 { 353 root = _resolver.resolveByPath(ROOT_SITES_PATH); 354 } 355 356 Site cSite = site.copyTo(root, siteName); 357 358 // Clear url and title 359 cSite.getMetadataHolder().removeMetadata("url"); 360 cSite.getMetadataHolder().removeMetadata("title"); 361 362 // Change reference to ametys object, re-init contents workflow, update site name, ... 363 _copySiteComponent.updateSiteAfterCopy(site, cSite); 364 365 return cSite; 366 367 } 368 369 370 /** 371 * Returns true if the given site exists. 372 * @param siteName the site name. 373 * @return true if the given site exists. 374 */ 375 public boolean hasSite(String siteName) 376 { 377 if (siteName == null) 378 { 379 return false; 380 } 381 382 Request request = _getRequest(); 383 if (request == null) 384 { 385 // There is no request to store cache 386 return _hasSite(siteName); 387 } 388 389 @SuppressWarnings("unchecked") 390 Map<String, Site> sitesCache = (Map<String, Site>) request.getAttribute(SITES_KEY); 391 if (sitesCache == null) 392 { 393 sitesCache = new HashMap<>(); 394 request.setAttribute(SITES_KEY, sitesCache); 395 } 396 397 // The site key in the cache is of the form "site/workspace". 398 String siteKey = siteName + "/" + _workspaceSelector.getWorkspace(); 399 400 Site site = sitesCache.get(siteKey); 401 if (site != null) 402 { 403 // Site is in cache 404 return true; 405 } 406 407 site = _getSite(siteName); 408 if (site != null) 409 { 410 // Store site in cache 411 sitesCache.put(siteKey, site); 412 return true; 413 } 414 415 return false; 416 } 417 418 private boolean _hasSite (String siteName) 419 { 420 String jcrQuery = "//element(" + siteName + ", ametys:site)"; 421 AmetysObjectIterable<Site> sites = _resolver.query(jcrQuery); 422 423 return sites.iterator().hasNext(); 424 } 425 426 /** 427 * Returns the named {@link Site}. 428 * @param siteName the site name. 429 * @return the named {@link Site}. 430 * @throws UnknownAmetysObjectException if the named site does not exist. 431 */ 432 public Site getSite(String siteName) throws UnknownAmetysObjectException 433 { 434 Request request = _getRequest(); 435 if (request == null) 436 { 437 // There is no request to store cache 438 return _getSite(siteName); 439 } 440 441 @SuppressWarnings("unchecked") 442 Map<String, Site> sitesCache = (Map<String, Site>) request.getAttribute(SITES_KEY); 443 if (sitesCache == null) 444 { 445 sitesCache = new HashMap<>(); 446 request.setAttribute(SITES_KEY, sitesCache); 447 } 448 449 // The site key in the cache is of the form "site/workspace". 450 String siteKey = siteName + "/" + _workspaceSelector.getWorkspace(); 451 452 Site site = sitesCache.get(siteKey); 453 if (site == null) 454 { 455 site = _getSite(siteName); 456 if (site != null) 457 { 458 // Store site in cache 459 sitesCache.put(siteKey, site); 460 } 461 } 462 463 return site; 464 } 465 466 /** 467 * Clear the site cache 468 */ 469 public void clearCache () 470 { 471 Request request = _getRequest(); 472 request.setAttribute(SITES_KEY, null); 473 } 474 475 private Site _getSite (String siteName) throws UnknownAmetysObjectException 476 { 477 String jcrQuery = "//element(" + siteName + ", ametys:site)"; 478 AmetysObjectIterable<Site> sites = _resolver.query(jcrQuery); 479 Iterator<Site> it = sites.iterator(); 480 481 if (it.hasNext()) 482 { 483 return it.next(); 484 } 485 486 return null; 487 } 488}