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