001/* 002 * Copyright 2015 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.Arrays; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Date; 023import java.util.HashMap; 024import java.util.List; 025import java.util.Map; 026 027import javax.jcr.RepositoryException; 028import javax.jcr.Session; 029 030import org.apache.avalon.framework.component.Component; 031import org.apache.avalon.framework.service.ServiceException; 032import org.apache.avalon.framework.service.ServiceManager; 033import org.apache.avalon.framework.service.Serviceable; 034import org.apache.commons.lang3.StringUtils; 035 036import org.ametys.core.group.GroupDirectoryContextHelper; 037import org.ametys.core.observation.Event; 038import org.ametys.core.observation.ObservationManager; 039import org.ametys.core.ui.Callable; 040import org.ametys.core.user.CurrentUserProvider; 041import org.ametys.core.user.population.PopulationContextHelper; 042import org.ametys.core.util.I18nUtils; 043import org.ametys.plugins.repository.AmetysObject; 044import org.ametys.plugins.repository.AmetysObjectIterable; 045import org.ametys.plugins.repository.AmetysObjectResolver; 046import org.ametys.plugins.repository.AmetysRepositoryException; 047import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 048import org.ametys.plugins.repository.UnknownAmetysObjectException; 049import org.ametys.plugins.repository.jcr.JCRAmetysObject; 050import org.ametys.plugins.repository.metadata.ModifiableCompositeMetadata; 051import org.ametys.runtime.i18n.I18nizableText; 052import org.ametys.runtime.parameter.Errors; 053import org.ametys.runtime.parameter.ParameterHelper; 054import org.ametys.runtime.parameter.ParameterHelper.ParameterType; 055import org.ametys.runtime.parameter.Validator; 056import org.ametys.runtime.plugin.component.AbstractLogEnabled; 057import org.ametys.web.ObservationConstants; 058import org.ametys.web.cache.CacheHelper; 059import org.ametys.web.cache.pageelement.PageElementCache; 060import org.ametys.web.live.RebuildLiveComponent; 061import org.ametys.web.repository.sitemap.Sitemap; 062import org.ametys.web.site.SiteConfigurationExtensionPoint; 063import org.ametys.web.site.SiteParameter; 064 065/** 066 * DAO for manipulating sites 067 * 068 */ 069public class SiteDAO extends AbstractLogEnabled implements Serviceable, Component 070{ 071 /** Avalon Role */ 072 public static final String ROLE = SiteDAO.class.getName(); 073 074 /** Id of the default site type */ 075 public static final String DEFAULT_SITE_TYPE_ID = "org.ametys.web.sitetype.Default"; 076 077 private static final List<String> __FORBIDDEN_SITE_NAMES = Arrays.asList("preview", "live", "archives", "generate"); 078 079 private SiteManager _siteManager; 080 private AmetysObjectResolver _resolver; 081 private ObservationManager _observationManager; 082 private CurrentUserProvider _currentUserProvider; 083 private PageElementCache _inputDataCache; 084 private PageElementCache _zoneItemCache; 085 private RebuildLiveComponent _rebuildLiveComponent; 086 private SiteConfigurationExtensionPoint _siteConfiguration; 087 private SiteTypesExtensionPoint _siteTypesEP; 088 private I18nUtils _i18nUtils; 089 private PopulationContextHelper _populationContextHelper; 090 private GroupDirectoryContextHelper _groupDirectoryContextHelper; 091 092 @Override 093 public void service(ServiceManager smanager) throws ServiceException 094 { 095 _siteManager = (SiteManager) smanager.lookup(SiteManager.ROLE); 096 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 097 _observationManager = (ObservationManager) smanager.lookup(ObservationManager.ROLE); 098 _currentUserProvider = (CurrentUserProvider) smanager.lookup(CurrentUserProvider.ROLE); 099 _inputDataCache = (PageElementCache) smanager.lookup(PageElementCache.ROLE + "/inputData"); 100 _zoneItemCache = (PageElementCache) smanager.lookup(PageElementCache.ROLE + "/zoneItem"); 101 _rebuildLiveComponent = (RebuildLiveComponent) smanager.lookup(RebuildLiveComponent.ROLE); 102 _siteConfiguration = (SiteConfigurationExtensionPoint) smanager.lookup(SiteConfigurationExtensionPoint.ROLE); 103 _siteTypesEP = (SiteTypesExtensionPoint) smanager.lookup(SiteTypesExtensionPoint.ROLE); 104 _i18nUtils = (I18nUtils) smanager.lookup(I18nUtils.ROLE); 105 _populationContextHelper = (PopulationContextHelper) smanager.lookup(PopulationContextHelper.ROLE); 106 _groupDirectoryContextHelper = (GroupDirectoryContextHelper) smanager.lookup(GroupDirectoryContextHelper.ROLE); 107 } 108 109 /** 110 * Get the root id 111 * @return the root id 112 */ 113 @Callable 114 public String getRootId () 115 { 116 return _siteManager.getRoot().getId(); 117 } 118 119 /** 120 * Get the properties of given sites 121 * @param names the site names 122 * @return the properties of the sites in a result map 123 */ 124 @Callable 125 public Map<String, Object> getSitesInfos(List<String> names) 126 { 127 Map<String, Object> result = new HashMap<>(); 128 129 List<Map<String, Object>> sites = new ArrayList<>(); 130 List<String> sitesNotFound = new ArrayList<>(); 131 132 for (String name : names) 133 { 134 try 135 { 136 Site site = _siteManager.getSite(name); 137 sites.add(getSiteInfos(site)); 138 } 139 catch (UnknownAmetysObjectException e) 140 { 141 sitesNotFound.add(name); 142 } 143 } 144 145 result.put("sites", sites); 146 result.put("sitesNotFound", sitesNotFound); 147 148 return result; 149 } 150 151 /** 152 * Get the site's properties 153 * @param name the site name 154 * @return the properties 155 */ 156 @Callable 157 public Map<String, Object> getSiteInfos(String name) 158 { 159 Site site = _siteManager.getSite(name); 160 return getSiteInfos(site); 161 } 162 163 /** 164 * Get the site's properties 165 * @param site the site 166 * @return the properties 167 */ 168 public Map<String, Object> getSiteInfos(Site site) 169 { 170 Map<String, Object> infos = new HashMap<>(); 171 172 infos.put("id", site.getId()); 173 infos.put("title", site.getTitle()); 174 infos.put("description", site.getDescription()); 175 infos.put("name", site.getName()); 176 infos.put("path", site.getSitePath()); 177 infos.put("url", site.getUrl()); 178 179 SiteType siteType = _siteTypesEP.getExtension(site.getType()); 180 infos.put("type", _i18nUtils.translate(siteType.getLabel())); 181 182 return infos; 183 } 184 185 /** 186 * Creates a new site 187 * @param parentId The id of parent site. Can be null to create a root site. 188 * @param name The site's name 189 * @param type The site's type 190 * @param renameIfExists Set to true to automatically rename the site if already exists 191 * @return The result map with id of created site 192 */ 193 @Callable 194 public Map<String, Object> createSite(String parentId, String name, String type, boolean renameIfExists) 195 { 196 Map<String, Object> result = new HashMap<>(); 197 198 if (__FORBIDDEN_SITE_NAMES.contains(name)) 199 { 200 // Name is invalid 201 result.put("name", name); 202 result.put("invalid-name", true); 203 } 204 else if (_siteManager.hasSite(name) && !renameIfExists) 205 { 206 // A site with same name already exists 207 result.put("name", name); 208 result.put("already-exists", true); 209 } 210 else 211 { 212 String siteParentId = null; 213 if (StringUtils.isNotEmpty(parentId)) 214 { 215 AmetysObject parent = _resolver.resolveById(parentId); 216 if (parent instanceof Site) 217 { 218 siteParentId = parent.getId(); 219 } 220 } 221 222 String siteName = name; 223 int index = 2; 224 while (_siteManager.hasSite(siteName)) 225 { 226 siteName = name + "-" + (index++); 227 } 228 229 // Create site 230 Site site = _siteManager.createSite(siteName, siteParentId); 231 site.setType(type); 232 site.saveChanges(); 233 234 result.put("id", site.getId()); 235 result.put("name", site.getName()); 236 237 if (siteParentId != null) 238 { 239 result.put("parentId", siteParentId); 240 } 241 242 // Notify observers 243 Map<String, Object> eventParams = new HashMap<>(); 244 eventParams.put(ObservationConstants.ARGS_SITE, site); 245 _observationManager.notify(new Event(ObservationConstants.EVENT_SITE_ADDED, _currentUserProvider.getUser(), eventParams)); 246 } 247 248 return result; 249 } 250 251 /** 252 * Create a site by copy of another. 253 * @param parentId The id of parent site. Can be null to create a root site. 254 * @param name the name of site to create 255 * @param id the id of site to copy 256 * @return The result map with id of created site 257 * @throws Exception if an error ocurred while populating new site 258 */ 259 @Callable 260 public Map<String, Object> copySite (String parentId, String name, String id) throws Exception 261 { 262 Map<String, Object> result = new HashMap<>(); 263 264 if (__FORBIDDEN_SITE_NAMES.contains(name)) 265 { 266 // Name is invalid 267 result.put("name", name); 268 result.put("invalid-name", true); 269 } 270 else if (_siteManager.hasSite(name)) 271 { 272 // A site with same name already exists 273 result.put("name", name); 274 result.put("already-exists", true); 275 } 276 else 277 { 278 Site site = _resolver.resolveById(id); 279 280 // Create site by copy 281 Site cSite = _siteManager.copySite(site, parentId, name); 282 cSite.saveChanges(); 283 284 // Notify observers 285 Map<String, Object> eventParams = new HashMap<>(); 286 eventParams.put(ObservationConstants.ARGS_SITE, cSite); 287 _observationManager.notify(new Event(ObservationConstants.EVENT_SITE_ADDED, _currentUserProvider.getUser(), eventParams)); 288 289 result.put("id", cSite.getId()); 290 result.put("name", cSite.getName()); 291 } 292 293 return result; 294 } 295 296 /** 297 * Delete a site 298 * @param siteId The id of site to delete 299 * @throws RepositoryException if an error occurred during deletion 300 */ 301 @Callable 302 public void deleteSite(String siteId) throws RepositoryException 303 { 304 Site site = _resolver.resolveById(siteId); 305 String siteName = site.getName(); 306 String jcrPath = site.getNode().getPath().substring(1); 307 Session session = site.getNode().getSession(); 308 309 Collection<String> siteNames = _getChildrenSiteNames(site); 310 311 site.remove(); 312 session.save(); 313 314 _siteConfiguration.removeConfiguration(siteName); 315 316 // Notify observers of site deletion 317 Map<String, Object> eventParams = new HashMap<>(); 318 eventParams.put(ObservationConstants.ARGS_SITE_ID, siteId); 319 eventParams.put(ObservationConstants.ARGS_SITE_NAME, siteName); 320 eventParams.put(ObservationConstants.ARGS_SITE_PATH, jcrPath); 321 eventParams.put("site.children", siteNames.toArray(new String[siteNames.size()])); 322 _observationManager.notify(new Event(ObservationConstants.EVENT_SITE_DELETED, _currentUserProvider.getUser(), eventParams)); 323 324 // Remove the links between this site and the populations and the group directories 325 String context = "/sites/" + siteName; 326 _populationContextHelper.link(context, Collections.EMPTY_LIST); 327 _groupDirectoryContextHelper.link(context, Collections.EMPTY_LIST); 328 } 329 330 /** 331 * Rebuild the live of a site, index all sitemaps and reset cache. 332 * @param siteName The site name 333 * @throws Exception if an error occurred 334 */ 335 @Callable 336 public void buildLive (String siteName) throws Exception 337 { 338 assert StringUtils.isEmpty(siteName); 339 340 Site site = _siteManager.getSite(siteName); 341 assert site != null; 342 343 _rebuildLiveComponent.rebuildLive(site); 344 } 345 346 /** 347 * Rebuild the live of a site, index all sitemaps and reset cache. 348 * @return The result map, containing the errors 349 * @throws Exception if an error occurred 350 */ 351 @Callable 352 public Map<String, Object> buildLive () throws Exception 353 { 354 Map<String, String> errors = new HashMap<>(_rebuildLiveComponent.rebuildLiveWorkspace()); 355 356 Map<String, Object> result = new HashMap<>(); 357 if (errors.size() > 0) 358 { 359 boolean hasGeneralError = errors.containsKey(null); 360 if (hasGeneralError) 361 { 362 errors.remove(null); 363 } 364 365 result.put("generalError", hasGeneralError); 366 result.put("errors", errors.keySet()); 367 } 368 369 return result; 370 } 371 372 /** 373 * Move sites 374 * @param targetId The target 375 * @param ids the ids of sites to move 376 * @return The result with the ids of moved sites 377 * @throws AmetysRepositoryException if an error occurs 378 * @throws RepositoryException if an error occurs 379 */ 380 @Callable 381 public Map<String, Object> moveSite (String targetId, List<String> ids) throws AmetysRepositoryException, RepositoryException 382 { 383 Map<String, Object> result = new HashMap<>(); 384 List<String> movedSites = new ArrayList<>(); 385 386 ModifiableTraversableAmetysObject root = _siteManager.getRoot(); 387 ModifiableTraversableAmetysObject target = _resolver.resolveById(targetId); 388 389 for (String id : ids) 390 { 391 Site site = _resolver.resolveById(id); 392 if (!site.getParent().equals(target)) 393 { 394 AmetysObject oldParent = site.getParent(); 395 site.moveTo(target, true); 396 397 // Path is modified 398 String newPath = site.getPath(); 399 400 if (root.needsSave()) 401 { 402 root.saveChanges(); 403 } 404 405 // Notify observers 406 Map<String, Object> eventParams = new HashMap<>(); 407 eventParams.put(ObservationConstants.ARGS_SITE, site); 408 eventParams.put(ObservationConstants.ARGS_SITE_PATH, newPath); 409 eventParams.put("site.old.path", ((JCRAmetysObject) oldParent).getNode().getPath() + "/" + site.getName()); 410 eventParams.put("site.parent", target); 411 _observationManager.notify(new Event(ObservationConstants.EVENT_SITE_MOVED, _currentUserProvider.getUser(), eventParams)); 412 413 movedSites.add(site.getId()); 414 } 415 } 416 417 result.put("ids", movedSites); 418 result.put("target", targetId); 419 420 return result; 421 } 422 423 424 /** 425 * Clear site's cache 426 * @param siteName The site name 427 * @throws Exception if an error occurred during cache deletion 428 */ 429 @Callable 430 public void clearCache (String siteName) throws Exception 431 { 432 assert StringUtils.isEmpty(siteName); 433 434 Site site = _siteManager.getSite(siteName); 435 assert site != null; 436 437 clearCache(site); 438 } 439 440 /** 441 * Clear cache of all sites. 442 * @return The list of sites which failed 443 */ 444 @Callable 445 public List<String> clearAllCaches () 446 { 447 List<String> errors = new ArrayList<>(); 448 449 AmetysObjectIterable<Site> sites = _siteManager.getSites(); 450 for (Site site : sites) 451 { 452 try 453 { 454 clearCache(site); 455 } 456 catch (Exception e) 457 { 458 getLogger().error("Unable to clear cache of site " + site.getName(), e); 459 errors.add(site.getName()); 460 } 461 } 462 463 return errors; 464 } 465 466 /** 467 * Clear cache of a site 468 * @param site the site 469 * @throws Exception if an error occurred 470 */ 471 public void clearCache (Site site) throws Exception 472 { 473 String siteName = site.getName(); 474 475 if (getLogger().isInfoEnabled()) 476 { 477 getLogger().info("Clearing cache for site " + siteName); 478 } 479 480 CacheHelper.invalidateCache(site, getLogger()); 481 _inputDataCache.clear(null, siteName); 482 _zoneItemCache.clear(null, siteName); 483 } 484 485 /** 486 * Configure site 487 * @param siteName The site name. 488 * @param values the configuration's values 489 * @return The result map. Contains the possible errors 490 * @throws Exception if an error occurred 491 */ 492 @Callable 493 public Map<String, Object> configureSite (String siteName, Map<String, Object> values) throws Exception 494 { 495 Map<String, Object> result = new HashMap<>(); 496 497 Site site = _siteManager.getSite(siteName); 498 499 // Site updating event 500 Map<String, Object> eventParams = new HashMap<>(); 501 eventParams.put(ObservationConstants.ARGS_SITE, site); 502 _observationManager.notify(new Event(ObservationConstants.EVENT_SITE_UPDATING, _currentUserProvider.getUser(), eventParams)); 503 504 Map<String, Errors> errors = new HashMap<>(); 505 506 _setParameters(site, values, errors); 507 508 if (!errors.isEmpty()) 509 { 510 List<Map<String, Object>> allErrors = new ArrayList<>(); 511 512 for (Map.Entry<String, Errors> entry : errors.entrySet()) 513 { 514 Map<String, Object> error = new HashMap<>(); 515 error.put("name", entry.getKey()); 516 517 List<I18nizableText> errorMessages = entry.getValue().getErrors(); 518 error.put("errorMessages", errorMessages); 519 520 allErrors.add(error); 521 } 522 523 result.put("errors", allErrors); 524 return result; 525 } 526 527 if (values.containsKey("lang")) 528 { 529 @SuppressWarnings("unchecked") 530 List<String> codes = (List<String>) values.get("lang"); 531 532 for (Sitemap sitemap : site.getSitemaps()) 533 { 534 String sitemapName = sitemap.getName(); 535 536 if (!codes.contains(sitemapName)) 537 { 538 sitemap.remove(); 539 540 eventParams.put(ObservationConstants.ARGS_SITEMAP_NAME, sitemapName); 541 _observationManager.notify(new Event(ObservationConstants.EVENT_SITEMAP_DELETED, _currentUserProvider.getUser(), eventParams)); 542 } 543 } 544 545 for (String code : codes) 546 { 547 if (!site.hasSitemap(code)) 548 { 549 Sitemap sitemap = site.addSitemap(code); 550 551 eventParams.put(ObservationConstants.ARGS_SITEMAP, sitemap); 552 _observationManager.notify(new Event(ObservationConstants.EVENT_SITEMAP_ADDED, _currentUserProvider.getUser(), eventParams)); 553 } 554 } 555 } 556 557 site.getNode().getSession().save(); 558 559 // Reload this site's configuration. 560 _siteConfiguration.reloadConfiguration(siteName); 561 562 // Site updated event 563 _observationManager.notify(new Event(ObservationConstants.EVENT_SITE_UPDATED, _currentUserProvider.getUser(), eventParams)); 564 565 clearCache(site); 566 567 return result; 568 } 569 570 /** 571 * Set the site parameters 572 * @param site the site 573 * @param values the parameters' values 574 * @param allErrors the parameters' errors 575 */ 576 protected void _setParameters(Site site, Map<String, Object> values, Map<String, Errors> allErrors) 577 { 578 ModifiableCompositeMetadata metaHolder = site.getMetadataHolder(); 579 580 for (SiteParameter parameter : _siteConfiguration.getParameters(site.getName()).values()) 581 { 582 String paramId = parameter.getId(); 583 584 if (!"lang".equals(paramId)) 585 { 586 String untypedValue = (String) values.get(paramId); 587 588 ParameterType type = parameter.getType(); 589 Object typedValue = ParameterHelper.castValue(untypedValue, type); 590 591 boolean hasErrors = false; 592 593 Validator validator = parameter.getValidator(); 594 if (validator != null) 595 { 596 Errors errors = new Errors(); 597 validator.validate(typedValue, errors); 598 599 if (errors.hasErrors()) 600 { 601 allErrors.put(paramId, errors); 602 hasErrors = true; 603 } 604 } 605 606 if (!hasErrors) 607 { 608 if (type == ParameterType.STRING) 609 { 610 metaHolder.setMetadata(paramId, typedValue == null ? "" : (String) typedValue); 611 } 612 else if (type == ParameterType.BOOLEAN) 613 { 614 metaHolder.setMetadata(paramId, typedValue == null ? false : (Boolean) typedValue); 615 } 616 else if (typedValue == null) 617 { 618 if (metaHolder.hasMetadata(paramId)) 619 { 620 metaHolder.removeMetadata(paramId); 621 } 622 } 623 else if (type == ParameterType.DATE) 624 { 625 metaHolder.setMetadata(paramId, (Date) typedValue); 626 } 627 else if (type == ParameterType.LONG) 628 { 629 metaHolder.setMetadata(paramId, (Long) typedValue); 630 } 631 else if (type == ParameterType.DOUBLE) 632 { 633 metaHolder.setMetadata(paramId, (Double) typedValue); 634 } 635 } 636 } 637 } 638 } 639 640 /** 641 * Get all children site's names of a site 642 * @param site The site 643 * @return the children site's names. 644 */ 645 private Collection<String> _getChildrenSiteNames (Site site) 646 { 647 ArrayList<String> result = new ArrayList<>(); 648 649 result.add(site.getName()); 650 651 AmetysObjectIterable<Site> sites = site.getChildrenSites(); 652 for (Site child : sites) 653 { 654 result.addAll(_getChildrenSiteNames(child)); 655 } 656 657 return result; 658 } 659}