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