001/* 002 * Copyright 2013 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 */ 016 017package org.ametys.web.live; 018 019import java.io.PrintWriter; 020import java.io.StringWriter; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.List; 024import java.util.Set; 025 026import javax.jcr.Repository; 027import javax.jcr.Session; 028 029import org.apache.avalon.framework.component.Component; 030import org.apache.avalon.framework.service.ServiceException; 031import org.apache.avalon.framework.service.ServiceManager; 032import org.apache.avalon.framework.service.Serviceable; 033import org.apache.commons.lang.StringUtils; 034import org.apache.solr.client.solrj.SolrClient; 035 036import org.ametys.cms.content.indexing.solr.SolrIndexer; 037import org.ametys.cms.indexing.IndexingException; 038import org.ametys.cms.indexing.WorkspaceIndexer; 039import org.ametys.cms.repository.Content; 040import org.ametys.cms.search.solr.SolrClientProvider; 041import org.ametys.core.cache.AbstractCacheManager; 042import org.ametys.plugins.repository.AmetysObjectIterable; 043import org.ametys.plugins.repository.AmetysObjectResolver; 044import org.ametys.plugins.repository.ChainedAmetysObjectIterable; 045import org.ametys.plugins.repository.CollectionIterable; 046import org.ametys.plugins.repository.collection.AmetysObjectCollection; 047import org.ametys.runtime.plugin.component.AbstractLogEnabled; 048import org.ametys.web.WebConstants; 049import org.ametys.web.cache.CacheHelper; 050import org.ametys.web.indexing.SiteIndexer; 051import org.ametys.web.repository.site.Site; 052import org.ametys.web.repository.site.SiteManager; 053import org.ametys.web.skin.Skin; 054import org.ametys.web.skin.SkinsManager; 055 056/** 057 * Component for rebuild the live workspace, reindex all sitemaps and reset cache. 058 * Provide a way to rebuild the full live workspace, or only the live of a site. 059 */ 060public class RebuildLiveComponent extends AbstractLogEnabled implements Component, Serviceable 061{ 062 /** Avalon Role. */ 063 public static final String ROLE = RebuildLiveComponent.class.getName(); 064 065 private AbstractCacheManager _cacheManager; 066 private Repository _repository; 067 private LivePopulatorExtensionPoint _livePopulatorExtensionPoint; 068 private SitePopulator _sitePopulator; 069 private SiteIndexer _siteIndexer; 070 private WorkspaceIndexer _workspaceIndexer; 071 private SiteManager _siteManager; 072 private SkinsManager _skinsManager; 073 private SolrIndexer _solrIndexer; 074 private SolrClientProvider _solrClientProvider; 075 private AmetysObjectResolver _resolver; 076 077 @Override 078 public void service(ServiceManager manager) throws ServiceException 079 { 080 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 081 _siteIndexer = (SiteIndexer) manager.lookup(SiteIndexer.ROLE); 082 _repository = (Repository) manager.lookup(Repository.class.getName()); 083 _livePopulatorExtensionPoint = (LivePopulatorExtensionPoint) manager.lookup(LivePopulatorExtensionPoint.ROLE); 084 _sitePopulator = (SitePopulator) manager.lookup(SitePopulator.ROLE); 085 _workspaceIndexer = (WorkspaceIndexer) manager.lookup(WorkspaceIndexer.ROLE); 086 _siteManager = (SiteManager) manager.lookup(SiteManager.ROLE); 087 _skinsManager = (SkinsManager) manager.lookup(SkinsManager.ROLE); 088 _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE); 089 _solrClientProvider = (SolrClientProvider) manager.lookup(SolrClientProvider.ROLE); 090 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 091 } 092 093 /** 094 * Rebuild live workspace, index all sitemaps and reset cache. 095 * @throws Exception if an error occurs. 096 */ 097 public void rebuildLiveWorkspace() throws Exception 098 { 099 List<String> errors = new ArrayList<>(); 100 101 try 102 { 103 getLogger().info("Rebuilding live workspace started"); 104 105 long t0 = System.currentTimeMillis(); 106 107 errors.addAll(_populateLiveWorkspace()); 108 errors.addAll(_reindexLiveWorkspace()); 109 110 errors.addAll(_clearAllSitesCache()); 111 112 _clearApplicationCache(); 113 114 getLogger().info("Rebuilding live workspace ended in {} ms with {} error(s)", System.currentTimeMillis() - t0, errors.size()); 115 } 116 catch (Exception e) 117 { 118 throw new Exception("Failed to rebuild live workspace", e); 119 } 120 121 if (errors.size() > 0) 122 { 123 throw new Exception("Failed to rebuild live workspace with " + errors.size() + " error(s)\n\n--------------------------------------\n" + StringUtils.join(errors, "\n\n") + "\n--------------------------------------"); 124 } 125 } 126 127 private List<String> _populateLiveWorkspace() 128 { 129 return _populate(_livePopulatorExtensionPoint.getExtensionsIds()); 130 } 131 132 private List<String> _populate(Set<String> populatorIds) 133 { 134 List<String> errors = new ArrayList<>(); 135 136 Session session = null; 137 Session liveSession = null; 138 139 try 140 { 141 getLogger().info("Populating live workspace started"); 142 143 long t0 = System.currentTimeMillis(); 144 145 session = _repository.login(); 146 liveSession = _repository.login(WebConstants.LIVE_WORKSPACE); 147 148 // Synchronize other data 149 for (String id : populatorIds) 150 { 151 _populate(id, session, liveSession, errors); 152 } 153 154 getLogger().info("Populating live workspace ended in {} ms", System.currentTimeMillis() - t0); 155 } 156 catch (Throwable t) 157 { 158 StringWriter sw = new StringWriter(); 159 t.printStackTrace(new PrintWriter(sw)); 160 161 errors.add("Failed to populate live workspace\n" + sw.toString()); 162 getLogger().error("Failed to populate live workspace", t); 163 } 164 finally 165 { 166 if (session != null) 167 { 168 session.logout(); 169 } 170 171 if (liveSession != null) 172 { 173 liveSession.logout(); 174 } 175 } 176 177 return errors; 178 } 179 180 private void _populate(String populatorId, Session session, Session liveSession, List<String> errors) 181 { 182 getLogger().info("Populating live workspace using LivePopulator {} started", populatorId); 183 184 try 185 { 186 LivePopulator populator = _livePopulatorExtensionPoint.getExtension(populatorId); 187 188 errors.addAll(populator.populate(session, liveSession)); 189 190 if (liveSession.hasPendingChanges()) 191 { 192 liveSession.save(); 193 } 194 } 195 catch (Throwable t) 196 { 197 errors.add("Failed to populate live workspace with populator '" + populatorId + "'\n" + t.getMessage()); 198 getLogger().error("Failed to populate live workspace with populator '" + populatorId + "'", t); 199 } 200 201 getLogger().info("Populating live workspace using LivePopulator {} ended", populatorId); 202 } 203 204 private List<String> _reindexLiveWorkspace() 205 { 206 List<String> errors = new ArrayList<>(); 207 208 try 209 { 210 getLogger().info("Indexing Live workspace started"); 211 212 long t0 = System.currentTimeMillis(); 213 214 // Index the whole live workspace and not just every site. 215 _workspaceIndexer.index(WebConstants.LIVE_WORKSPACE); 216 217 getLogger().info("Indexing live workspace ended in {} ms", System.currentTimeMillis() - t0); 218 } 219 catch (IndexingException e) 220 { 221 errors.add("Failed to index live workspace\n" + e.getMessage()); 222 getLogger().error("Failed to index live workspace", e); 223 } 224 225 return errors; 226 } 227 228 private List<String> _clearAllSitesCache() 229 { 230 List<String> errors = new ArrayList<>(); 231 try (AmetysObjectIterable<Site> siteIterable = _siteManager.getSites()) 232 { 233 for (Site site : siteIterable) 234 { 235 try 236 { 237 // TODO Clear only the live workspace? 238 _clearSiteCache(site); 239 } 240 catch (Throwable t) 241 { 242 errors.add("Failed to clear cache of site '" + site.getName() + "'\n" + t.getMessage()); 243 getLogger().error("Failed to clear cache of site '" + site.getName() + "'", t); 244 } 245 } 246 } 247 return errors; 248 } 249 250 private void _clearSiteCache(Site site) throws Exception 251 { 252 String siteName = site.getName(); 253 254 getLogger().info("Clearing cache for site {} started", siteName); 255 256 long t0 = System.currentTimeMillis(); 257 258 CacheHelper.invalidateCache(site, getLogger()); 259 260 getLogger().info("Clearing cache for site '{}' ended in {} ms", siteName, System.currentTimeMillis() - t0); 261 } 262 263 private void _clearApplicationCache() 264 { 265 _cacheManager.resetAllMemoryCaches(); 266 } 267 268 /** 269 * Rebuild the live of a site, index all sitemaps and reset cache. 270 * @param site The site to be rebuilt 271 * @throws Exception if an error occurs. 272 */ 273 public void rebuildLive(Site site) throws Exception 274 { 275 String siteName = ""; 276 try 277 { 278 siteName = site.getName(); 279 280 Skin skin = _skinsManager.getSkin(site.getSkinId()); 281 assert skin != null; 282 283 getLogger().info("Rebuilding site {} started", siteName); 284 285 long t0 = System.currentTimeMillis(); 286 287 _populateSite(site, skin); 288 _reindexSite(site); 289 _clearSiteCache(site); 290 _clearApplicationCache(); 291 292 getLogger().info("Rebuilding site {} ended in {} ms", siteName, System.currentTimeMillis() - t0); 293 } 294 catch (Exception e) 295 { 296 throw new Exception("Failed to rebuild live workspace for site '" + siteName + "'", e); 297 } 298 } 299 300 private void _populateSite(Site site, Skin skin) throws Exception 301 { 302 String siteName = site.getName(); 303 304 getLogger().info("Populating site {} started", siteName); 305 306 long t0 = System.currentTimeMillis(); 307 308 _sitePopulator.populate(site, skin); 309 310 getLogger().info("Populating site {} ended in {} ms", siteName, System.currentTimeMillis() - t0); 311 } 312 313 private void _reindexSite(Site site) throws Exception 314 { 315 String siteName = site.getName(); 316 317 getLogger().info("Indexing site '{}' started", siteName); 318 319 long t0 = System.currentTimeMillis(); 320 321 // Reindex the site in the live workspace. 322 _siteIndexer.indexSite(site.getName(), WebConstants.LIVE_WORKSPACE); 323 324 getLogger().info("Indexing of site '{}' ended in {} ms", siteName, System.currentTimeMillis() - t0); 325 } 326 327 /** 328 * Rebuild the live workspace for contents without site. 329 * 330 * More precisely, this method will synchronize and reindex all contents located 331 * at '/ametys:root/ametys:contents' and '/ametys:root/ametys:plugins/<pluginName>/ametys:contents' 332 * @throws Exception if an error occurred 333 */ 334 public void rebuildContentsWithoutSiteWorkspace() throws Exception 335 { 336 List<String> errors = new ArrayList<>(); 337 338 try 339 { 340 getLogger().info("Rebuilding live workspace started"); 341 342 long t0 = System.currentTimeMillis(); 343 344 // call ContentsLivePopulator and PluginsLivePopulator 345 errors.addAll(_populate(Set.of(ContentsLivePopulator.class.getName(), PluginsLivePopulator.class.getName()))); 346 // reindex impacted contents 347 errors.addAll(_reindexContentWithoutSiteLiveWorkspace()); 348 349 // Clear every site's cache. 350 errors.addAll(_clearAllSitesCache()); 351 352 // Clear application cache to make sur that no outdated info is kept in cache 353 _clearApplicationCache(); 354 355 getLogger().info("Rebuilding live workspace ended in {} ms with {} error(s)", System.currentTimeMillis() - t0, errors.size()); 356 } 357 catch (Exception e) 358 { 359 throw new Exception("Failed to rebuild live workspace", e); 360 } 361 362 if (errors.size() > 0) 363 { 364 throw new Exception("Failed to rebuild live workspace with " + errors.size() + " error(s)\n\n--------------------------------------\n" + StringUtils.join(errors, "\n\n") + "\n--------------------------------------"); 365 } 366 } 367 368 private Collection< ? extends String> _reindexContentWithoutSiteLiveWorkspace() throws Exception 369 { 370 List<String> errors = new ArrayList<>(); 371 372 try 373 { 374 getLogger().info("Indexing contents without site live workspace started"); 375 376 long t0 = System.currentTimeMillis(); 377 378 // Do not close the client, this is the provider responsibility 379 SolrClient solrClient = _solrClientProvider.getUpdateClient(WebConstants.LIVE_WORKSPACE, false); 380 try (AmetysObjectIterable<AmetysObjectCollection> roots = _getContentsWithoutSiteRootNodes()) 381 { 382 for (AmetysObjectCollection root : roots) 383 { 384 getLogger().info("Indexing live contents at path {}", root.getPath()); 385 try (AmetysObjectIterable<Content> contents = root.getChildren()) 386 { 387 _solrIndexer.indexContents(contents, WebConstants.LIVE_WORKSPACE, true, solrClient); 388 _solrIndexer.commit(WebConstants.LIVE_WORKSPACE, solrClient); 389 } 390 getLogger().info("Indexing live contents at path {} ended", root.getPath()); 391 } 392 } 393 394 getLogger().info("Indexing contents witout site live workspace ended in {} ms", System.currentTimeMillis() - t0); 395 } 396 catch (IndexingException e) 397 { 398 errors.add("Failed to index contents without site live workspace\n" + e.getMessage()); 399 getLogger().error("Failed to index contents without site live workspace", e); 400 } 401 402 return errors; 403 } 404 405 private AmetysObjectIterable<AmetysObjectCollection> _getContentsWithoutSiteRootNodes() 406 { 407 // contents nodes in plugins 408 AmetysObjectIterable<AmetysObjectCollection> roots = _resolver.<AmetysObjectCollection>query("/jcr:root/ametys:root/ametys:plugins/*/ametys:contents"); 409 // contents node at ametys root 410 AmetysObjectIterable<AmetysObjectCollection> rootIterable = new CollectionIterable<>(List.of(_resolver.resolveByPath("/ametys:contents"))); 411 412 // Return an iterable so that the consumer is responsible for closing 413 return new ChainedAmetysObjectIterable<>(List.of(rootIterable, roots)); 414 } 415}