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 Object oldValue = null; 212 213 Request request = _getRequest(); 214 try 215 { 216 oldValue = request.getAttribute("siteName"); 217 request.setAttribute("siteName", siteName); // Setting temporarily this attribute to check user rights on any object on this site 218 return !_rightManager.getUserRights(user, "/cms").isEmpty(); 219 } 220 finally 221 { 222 request.setAttribute("siteName", oldValue); 223 } 224 } 225 226 /** 227 * Returns the root for sites. 228 * @return the root for sites. 229 */ 230 public ModifiableTraversableAmetysObject getRoot() 231 { 232 return _resolver.resolveByPath(ROOT_SITES_PATH); 233 } 234 235 /** 236 * Returns the root sites. 237 * @return the root sites. 238 */ 239 public AmetysObjectIterable<Site> getRootSites() 240 { 241 TraversableAmetysObject root = _resolver.resolveByPath(ROOT_SITES_PATH); 242 return root.getChildren(); 243 } 244 245 /** 246 * Returns the all sites. 247 * @return the all sites. 248 */ 249 public AmetysObjectIterable<Site> getSites() 250 { 251 String jcrQuery = "//element(*, ametys:site)"; 252 return _resolver.query(jcrQuery); 253 } 254 255 /** 256 * Creates a site with the given name. 257 * @param siteName the site name. 258 * @param parentId the id of the parent site. Can be null. 259 * @return the newly created {@link Site}. 260 * @throws RepositoryIntegrityViolationException if the named site already exists. 261 */ 262 public Site createSite(String siteName, String parentId) throws RepositoryIntegrityViolationException 263 { 264 ModifiableTraversableAmetysObject root = null; 265 if (parentId != null) 266 { 267 Site site = _resolver.resolveById(parentId); 268 if (!site.hasChild(ROOT_SITES)) 269 { 270 site.createChild(ROOT_SITES, "ametys:sites"); 271 } 272 root = site.getChild(ROOT_SITES); 273 } 274 else 275 { 276 root = _resolver.resolveByPath(ROOT_SITES_PATH); 277 } 278 279 Site site = (Site) root.createChild(siteName, "ametys:site"); 280 try 281 { 282 String sitePath = site.getNode().getPath(); 283 284 // Create the resource explorer root. 285 site.getNode().addNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources", "ametys:resources-collection"); 286 287 Session session = _adminSessionProvider.getAdminSession(); 288 ObservationManager manager = session.getWorkspace().getObservationManager(); 289 290 manager.addEventListener(new LiveWorkspaceListener(_repository, _synchronizeComponent, getLogger()), 291 Event.NODE_ADDED 292 | Event.NODE_REMOVED 293 | Event.NODE_MOVED 294 | Event.PROPERTY_ADDED 295 | Event.PROPERTY_CHANGED 296 | Event.PROPERTY_REMOVED, 297 sitePath + "/ametys-internal:plugins", true, null, null, false); 298 299 manager.addEventListener(new LiveWorkspaceListener(_repository, _synchronizeComponent, getLogger()), 300 Event.NODE_ADDED 301 | Event.NODE_REMOVED 302 | Event.NODE_MOVED 303 | Event.PROPERTY_ADDED 304 | Event.PROPERTY_CHANGED 305 | Event.PROPERTY_REMOVED, 306 sitePath + "/ametys-internal:resources", true, null, null, false); 307 } 308 catch (RepositoryException ex) 309 { 310 throw new AmetysRepositoryException(ex); 311 } 312 313 return site; 314 } 315 316 /** 317 * Creates a site with the given name. 318 * @param siteName the site name. 319 * @return the newly created {@link Site}. 320 * @throws RepositoryIntegrityViolationException if the named site already exists. 321 */ 322 public Site createSite(String siteName) throws RepositoryIntegrityViolationException 323 { 324 return createSite (siteName, null); 325 } 326 327 /** 328 * Creates a site with the given name, from another site to copy 329 * @param site the site to be copied 330 * @param siteName the site name 331 * @return the newly created {@link Site}. 332 * @throws RepositoryIntegrityViolationException if the named site already exists. 333 */ 334 public Site copySite(Site site, String siteName) throws RepositoryIntegrityViolationException 335 { 336 return copySite(site, null, siteName); 337 } 338 339 /** 340 * Creates a site with the given name, from another site to copy 341 * @param site the site to be copied 342 * @param parentId the id of the parent site. Can be null. 343 * @param siteName the site name 344 * @return the newly created {@link Site}. 345 * @throws RepositoryIntegrityViolationException if the named site already exists. 346 */ 347 public Site copySite(Site site, String parentId, String siteName) throws RepositoryIntegrityViolationException 348 { 349 ModifiableTraversableAmetysObject root = null; 350 if (parentId != null) 351 { 352 root = _resolver.resolveById(parentId); 353 } 354 else 355 { 356 root = _resolver.resolveByPath(ROOT_SITES_PATH); 357 } 358 359 Site cSite = site.copyTo(root, siteName); 360 361 // Clear url and title 362 cSite.removeValue("url"); 363 cSite.removeValue("title"); 364 365 // Change reference to ametys object, re-init contents workflow, update site name, ... 366 _copySiteComponent.updateSiteAfterCopy(site, cSite); 367 368 return cSite; 369 370 } 371 372 373 /** 374 * Returns true if the given site exists. 375 * @param siteName the site name. 376 * @return true if the given site exists. 377 */ 378 public boolean hasSite(String siteName) 379 { 380 if (siteName == null) 381 { 382 return false; 383 } 384 385 Request request = _getRequest(); 386 if (request == null) 387 { 388 // There is no request to store cache 389 return _hasSite(siteName); 390 } 391 392 @SuppressWarnings("unchecked") 393 Map<String, Site> sitesCache = (Map<String, Site>) request.getAttribute(SITES_KEY); 394 if (sitesCache == null) 395 { 396 sitesCache = new HashMap<>(); 397 request.setAttribute(SITES_KEY, sitesCache); 398 } 399 400 // The site key in the cache is of the form "site/workspace". 401 String siteKey = siteName + "/" + _workspaceSelector.getWorkspace(); 402 403 Site site = sitesCache.get(siteKey); 404 if (site != null) 405 { 406 // Site is in cache 407 return true; 408 } 409 410 site = _getSite(siteName); 411 if (site != null) 412 { 413 // Store site in cache 414 sitesCache.put(siteKey, site); 415 return true; 416 } 417 418 return false; 419 } 420 421 private boolean _hasSite (String siteName) 422 { 423 String jcrQuery = "//element(" + siteName + ", ametys:site)"; 424 AmetysObjectIterable<Site> sites = _resolver.query(jcrQuery); 425 426 return sites.iterator().hasNext(); 427 } 428 429 /** 430 * Returns the named {@link Site}. 431 * @param siteName the site name. 432 * @return the named {@link Site}. 433 * @throws UnknownAmetysObjectException if the named site does not exist. 434 */ 435 public Site getSite(String siteName) throws UnknownAmetysObjectException 436 { 437 Request request = _getRequest(); 438 if (request == null) 439 { 440 // There is no request to store cache 441 return _getSite(siteName); 442 } 443 444 @SuppressWarnings("unchecked") 445 Map<String, Site> sitesCache = (Map<String, Site>) request.getAttribute(SITES_KEY); 446 if (sitesCache == null) 447 { 448 sitesCache = new HashMap<>(); 449 request.setAttribute(SITES_KEY, sitesCache); 450 } 451 452 // The site key in the cache is of the form "site/workspace". 453 String siteKey = siteName + "/" + _workspaceSelector.getWorkspace(); 454 455 Site site = sitesCache.get(siteKey); 456 if (site == null) 457 { 458 site = _getSite(siteName); 459 if (site != null) 460 { 461 // Store site in cache 462 sitesCache.put(siteKey, site); 463 } 464 } 465 466 return site; 467 } 468 469 /** 470 * Clear the site cache 471 */ 472 public void clearCache () 473 { 474 Request request = _getRequest(); 475 request.setAttribute(SITES_KEY, null); 476 } 477 478 private Site _getSite (String siteName) throws UnknownAmetysObjectException 479 { 480 String jcrQuery = "//element(" + siteName + ", ametys:site)"; 481 AmetysObjectIterable<Site> sites = _resolver.query(jcrQuery); 482 Iterator<Site> it = sites.iterator(); 483 484 if (it.hasNext()) 485 { 486 return it.next(); 487 } 488 489 return null; 490 } 491}