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.live; 017 018import java.util.concurrent.locks.Lock; 019 020import javax.jcr.Node; 021import javax.jcr.PathNotFoundException; 022import javax.jcr.Repository; 023import javax.jcr.RepositoryException; 024import javax.jcr.Session; 025import javax.jcr.query.Query; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.commons.collections.PredicateUtils; 032import org.apache.jackrabbit.util.ISO9075; 033 034import org.ametys.cms.repository.Content; 035import org.ametys.cms.support.AmetysPredicateUtils; 036import org.ametys.core.schedule.progression.ContainerProgressionTracker; 037import org.ametys.core.schedule.progression.ProgressionTrackerFactory; 038import org.ametys.core.schedule.progression.SimpleProgressionTracker; 039import org.ametys.plugins.repository.AmetysObjectIterable; 040import org.ametys.plugins.repository.RepositoryConstants; 041import org.ametys.runtime.i18n.I18nizableText; 042import org.ametys.runtime.plugin.component.AbstractLogEnabled; 043import org.ametys.web.WebConstants; 044import org.ametys.web.repository.site.Site; 045import org.ametys.web.repository.sitemap.Sitemap; 046import org.ametys.web.skin.Skin; 047import org.ametys.web.synchronization.SynchronizeComponent; 048 049/** 050 * Component for populating the live workspace site by site. 051 */ 052public class SitePopulator extends AbstractLogEnabled implements Serviceable, Component 053{ 054 /** Avalon role. */ 055 public static final String ROLE = SitePopulator.class.getName(); 056 057 private Repository _repository; 058 private LiveAccessManager _liveAccessManager; 059 060 private SynchronizeComponent _synchronizeComponent; 061 062 @Override 063 public void service(ServiceManager manager) throws ServiceException 064 { 065 _repository = (Repository) manager.lookup(Repository.class.getName()); 066 _liveAccessManager = (LiveAccessManager) manager.lookup(LiveAccessManager.ROLE); 067 _synchronizeComponent = (SynchronizeComponent) manager.lookup(SynchronizeComponent.ROLE); 068 } 069 070 /** 071 * Populates the live workspace. 072 * @param site the site to populate. 073 * @param skin the site skin model 074 * @throws Exception if an error occurs. 075 */ 076 public void populate(Site site, Skin skin) throws Exception 077 { 078 populate(site, skin, ProgressionTrackerFactory.createContainerProgressionTracker(new I18nizableText("Populate site '" + site.getName() + "'"), getLogger())); 079 } 080 081 /** 082 * Populates the live workspace. 083 * @param site the site to populate. 084 * @param skin the site skin model 085 * @param progressionTracker The progression tracker 086 * @throws Exception if an error occurs. 087 */ 088 public void populate(Site site, Skin skin, ContainerProgressionTracker progressionTracker) throws Exception 089 { 090 Lock lock = _liveAccessManager.getLiveWriteLock(); 091 092 // Ensure write access to the live workspace is exclusive 093 lock.lock(); 094 095 try 096 { 097 if (getLogger().isDebugEnabled()) 098 { 099 getLogger().debug("Live write lock has been acquired: " + lock); 100 } 101 102 Session liveSession = null; 103 104 try 105 { 106 // Open a session to the workspace live 107 liveSession = _repository.login(WebConstants.LIVE_WORKSPACE); 108 109 _populate(liveSession, site, skin, progressionTracker); 110 111 liveSession.save(); 112 } 113 finally 114 { 115 if (liveSession != null) 116 { 117 liveSession.logout(); 118 } 119 } 120 } 121 finally 122 { 123 lock.unlock(); 124 } 125 } 126 127 private void _createProgressionTrackerStepsForPopulate(Skin skin, ContainerProgressionTracker progressionTracker) 128 { 129 progressionTracker.addSimpleStep("contents", new I18nizableText("plugin.web", "PLUGINS_WEB_SCHEDULABLE_REBUILD_LIVE_CONTENTS_SYNC_STEP_LABEL")); 130 131 // Clone sitemaps 132 if (skin != null) 133 { 134 progressionTracker.addSimpleStep("sitemaps", new I18nizableText("plugin.web", "PLUGINS_WEB_SCHEDULABLE_REBUILD_LIVE_CLONE_SITEMAPS_STEP_LABEL")); 135 } 136 } 137 138 /** 139 * Populates a site 140 * @param liveSession the live workspace session. 141 * @param site the site to populate. 142 * @param skin the site skin model 143 * @param progressionTracker The progression tracker 144 * @throws RepositoryException if an error occurs. 145 */ 146 protected void _populate(Session liveSession, Site site, Skin skin, ContainerProgressionTracker progressionTracker) throws RepositoryException 147 { 148 _createProgressionTrackerStepsForPopulate(skin, progressionTracker); 149 150 Node siteNode = site.getNode(); 151 String siteNodePath = siteNode.getPath().substring(1); 152 Node liveRootNode = liveSession.getRootNode(); 153 154 Node clonedSiteNode = _cloneSiteNode(liveSession, site, siteNode, siteNodePath, liveRootNode); 155 156 // Clone only properties 157 _cloneProperties(siteNode, clonedSiteNode); 158 159 // contents 160 _cloneContents(liveSession, site, siteNode, clonedSiteNode, progressionTracker.getStep("contents")); 161 162 // Clone sitemaps 163 if (skin != null) 164 { 165 // The skin can be null if site was created but not yet configured 166 _cloneSitemaps(liveSession, site, skin, siteNode, clonedSiteNode, progressionTracker.getStep("sitemaps")); 167 } 168 } 169 170 /** 171 * Clone a site node in the live workspace. 172 * @param liveSession the live session. 173 * @param site the source site. 174 * @param siteNode the site node to clone. 175 * @param siteNodePath the site node path. 176 * @param liveRootNode the live root node. 177 * @return the cloned site node. 178 * @throws RepositoryException if an error occurs. 179 */ 180 private Node _cloneSiteNode(Session liveSession, Site site, Node siteNode, String siteNodePath, Node liveRootNode) throws RepositoryException 181 { 182 Node clonedSiteNode = null; 183 if (liveRootNode.hasNode(siteNodePath)) 184 { 185 clonedSiteNode = liveRootNode.getNode(siteNodePath); 186 } 187 else 188 { 189 getLogger().debug("Creating site '{}' in live worspace", site.getName()); 190 191 Node sitesNode = _synchronizeComponent.cloneAncestorsAndPreserveUUID(siteNode, liveSession); 192 clonedSiteNode = _synchronizeComponent.addNodeWithUUID(siteNode, sitesNode, siteNode.getName()); 193 } 194 195 // Remove existing resources // or auto-created node with wrong UUID 196 if (clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources")) 197 { 198 getLogger().debug("Removing resources' node for site '{}'", site.getName()); 199 clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources").remove(); 200 } 201 202 // Remove plugins // or auto-created node with wrong UUID 203 if (clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins")) 204 { 205 getLogger().debug("Removing plugins' node for site '{}'", site.getName()); 206 clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins").remove(); 207 } 208 209 // Remove existing sitemaps // or auto-created node with wrong UUID 210 if (clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps")) 211 { 212 getLogger().debug("Removing sitemaps' node for site '{}'", site.getName()); 213 clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps").remove(); 214 } 215 216 // Remove existing contents // or auto-created node with wrong UUID 217 if (clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents")) 218 { 219 getLogger().debug("Removing contents' node for site '{}'", site.getName()); 220 clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents").remove(); 221 } 222 223 return clonedSiteNode; 224 } 225 226 /** 227 * Copy all properties from a site node to another. 228 * @param siteNode the site JCR node 229 * @param clonedSiteNode the JCR node of site to copy 230 * @throws RepositoryException if an error occurred during copy 231 */ 232 private void _cloneProperties(Node siteNode, Node clonedSiteNode) throws RepositoryException 233 { 234 // Remove then clone all properties and others nodes prefixed by 'ametys:' 235 _synchronizeComponent.cloneNodeAndPreserveUUID(siteNode, clonedSiteNode, PredicateUtils.truePredicate(), AmetysPredicateUtils.ametysAttributePredicate()); 236 237 // resources 238 Node resourcesNode = siteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources"); 239 Node clonedResourcesNode = _synchronizeComponent.addNodeWithUUID(resourcesNode, clonedSiteNode, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources"); 240 _synchronizeComponent.cloneNodeAndPreserveUUID(resourcesNode, clonedResourcesNode, PredicateUtils.truePredicate(), PredicateUtils.truePredicate()); 241 242 // plugins 243 Node pluginsNode = siteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins"); 244 Node clonedPluginsNode = null; 245 if (!clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins")) 246 { 247 clonedPluginsNode = _synchronizeComponent.addNodeWithUUID(pluginsNode, clonedSiteNode, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins"); 248 } 249 else 250 { 251 clonedPluginsNode = clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins"); 252 } 253 _synchronizeComponent.cloneNodeAndPreserveUUID(pluginsNode, clonedPluginsNode, PredicateUtils.truePredicate(), PredicateUtils.truePredicate()); 254 } 255 256 private void _cloneContents(Session liveSession, Site site, Node siteNode, Node clonedSiteNode, SimpleProgressionTracker progressionTracker) throws RepositoryException 257 { 258 // The contents root has to be cloned even if it doesn't contain any content. 259 Node contentsNode = siteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents"); 260 if (!clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents")) 261 { 262 _synchronizeComponent.addNodeWithUUID(contentsNode, clonedSiteNode, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents"); 263 progressionTracker.setSize(0); 264 } 265 266 _synchronizeComponent.synchronizeACL(contentsNode, liveSession); 267 268 // Clone the contents. 269 try (AmetysObjectIterable<Content> contents = site.getContents()) 270 { 271 if (getLogger().isInfoEnabled()) 272 { 273 getLogger().info("Synchronizing " + contents.getSize() + " contents"); 274 } 275 276 _synchronizeComponent.synchronizeContents(contents, liveSession, progressionTracker); 277 } 278 } 279 280 @SuppressWarnings("deprecation") 281 private void _cloneSitemaps(Session liveSession, Site site, Skin skin, Node siteNode, Node clonedSiteNode, SimpleProgressionTracker progressionTracker) throws PathNotFoundException, RepositoryException 282 { 283 // Count pages 284 long size = siteNode.getSession().getWorkspace().getQueryManager().createQuery("/jcr:root" + ISO9075.encodePath(siteNode.getPath()) + "/ametys-internal:sitemaps//element(*, ametys:page)", Query.XPATH).execute().getNodes().getSize(); 285 progressionTracker.setSize(size); 286 287 Node sitemapsNode = siteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps"); 288 Node clonedSitemapsNode = null; 289 if (!clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps")) 290 { 291 clonedSitemapsNode = _synchronizeComponent.addNodeWithUUID(sitemapsNode, clonedSiteNode, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps"); 292 } 293 else 294 { 295 clonedSitemapsNode = clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps"); 296 } 297 298 for (Sitemap sitemap : site.getSitemaps()) 299 { 300 _populate(liveSession, clonedSitemapsNode, sitemap, skin, progressionTracker); 301 } 302 } 303 304 /** 305 * Populates a sitemap. 306 * @param liveSession The live session 307 * @param clonedSitemapsNode the cloned sitemaps node. 308 * @param sitemap the sitemap to populate. 309 * @param skin the site skin model 310 * @param progressionTracker The tracker for pages 311 * @throws RepositoryException if an error occurs. 312 */ 313 protected void _populate(Session liveSession, Node clonedSitemapsNode, Sitemap sitemap, Skin skin, SimpleProgressionTracker progressionTracker) throws RepositoryException 314 { 315 if (getLogger().isDebugEnabled()) 316 { 317 getLogger().debug("Creating sitemap: " + sitemap); 318 } 319 320 Node sitemapNode = sitemap.getNode(); 321 Node clonedSitemapNode = _synchronizeComponent.addNodeWithUUID(sitemapNode, clonedSitemapsNode, sitemapNode.getName()); 322 323 _synchronizeComponent.synchronizeACL(sitemapNode, liveSession); 324 325 // Clone only properties 326 _synchronizeComponent.cloneNodeAndPreserveUUID(sitemapNode, clonedSitemapNode, PredicateUtils.truePredicate(), PredicateUtils.falsePredicate()); 327 328 // Clone zones 329 _synchronizeComponent.cloneZones(sitemapNode, sitemap, clonedSitemapNode); 330 331 // Clone eligible pages 332 _synchronizeComponent.cloneEligibleChildrenPages(sitemap, skin, clonedSitemapsNode.getSession(), progressionTracker); 333 } 334}