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.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 } 156 else 157 { 158 getLogger().debug("Creating site '{}' in live worspace", site.getName()); 159 160 Node sitesNode = _synchronizeComponent.cloneAncestorsAndPreserveUUID(siteNode, liveSession); 161 clonedSiteNode = _synchronizeComponent.addNodeWithUUID(siteNode, sitesNode, siteNode.getName()); 162 } 163 164 // Remove existing resources // or auto-created node with wrong UUID 165 if (clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources")) 166 { 167 getLogger().debug("Removing resources' node for site '{}'", site.getName()); 168 clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources").remove(); 169 } 170 171 // Remove plugins // or auto-created node with wrong UUID 172 if (clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins")) 173 { 174 getLogger().debug("Removing plugins' node for site '{}'", site.getName()); 175 clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins").remove(); 176 } 177 178 // Remove existing sitemaps // or auto-created node with wrong UUID 179 if (clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps")) 180 { 181 getLogger().debug("Removing sitemaps' node for site '{}'", site.getName()); 182 clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps").remove(); 183 } 184 185 // Remove existing contents // or auto-created node with wrong UUID 186 if (clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents")) 187 { 188 getLogger().debug("Removing contents' node for site '{}'", site.getName()); 189 clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents").remove(); 190 } 191 192 return clonedSiteNode; 193 } 194 195 /** 196 * Copy all properties from a site node to another. 197 * @param siteNode the site JCR node 198 * @param clonedSiteNode the JCR node of site to copy 199 * @throws RepositoryException if an error occurred during copy 200 */ 201 private void _cloneProperties(Node siteNode, Node clonedSiteNode) throws RepositoryException 202 { 203 // Remove then clone all properties and others nodes prefixed by 'ametys:' 204 _synchronizeComponent.cloneNodeAndPreserveUUID(siteNode, clonedSiteNode, PredicateUtils.truePredicate(), AmetysPredicateUtils.ametysAttributePredicate()); 205 206 // resources 207 Node resourcesNode = siteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources"); 208 Node clonedResourcesNode = _synchronizeComponent.addNodeWithUUID(resourcesNode, clonedSiteNode, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":resources"); 209 _synchronizeComponent.cloneNodeAndPreserveUUID(resourcesNode, clonedResourcesNode, PredicateUtils.truePredicate(), PredicateUtils.truePredicate()); 210 211 // plugins 212 Node pluginsNode = siteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins"); 213 Node clonedPluginsNode = null; 214 if (!clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins")) 215 { 216 clonedPluginsNode = _synchronizeComponent.addNodeWithUUID(pluginsNode, clonedSiteNode, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins"); 217 } 218 else 219 { 220 clonedPluginsNode = clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":plugins"); 221 } 222 _synchronizeComponent.cloneNodeAndPreserveUUID(pluginsNode, clonedPluginsNode, PredicateUtils.truePredicate(), PredicateUtils.truePredicate()); 223 } 224 225 private void _cloneContents(Session liveSession, Site site, Node siteNode, Node clonedSiteNode) throws RepositoryException 226 { 227 // The contents root has to be cloned even if it doesn't contain any content. 228 Node contentsNode = siteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents"); 229 if (!clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents")) 230 { 231 _synchronizeComponent.addNodeWithUUID(contentsNode, clonedSiteNode, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":contents"); 232 } 233 234 _synchronizeComponent.synchronizeACL(contentsNode, liveSession); 235 236 // Clone the contents. 237 try (AmetysObjectIterable<Content> contents = site.getContents()) 238 { 239 if (getLogger().isInfoEnabled()) 240 { 241 getLogger().info("Synchronizing " + contents.getSize() + " contents"); 242 } 243 244 _synchronizeComponent.synchronizeContents(contents, liveSession); 245 } 246 } 247 248 private void _cloneSitemaps(Session liveSession, Site site, Skin skin, Node siteNode, Node clonedSiteNode) throws PathNotFoundException, RepositoryException 249 { 250 Node sitemapsNode = siteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps"); 251 Node clonedSitemapsNode = null; 252 if (!clonedSiteNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps")) 253 { 254 clonedSitemapsNode = _synchronizeComponent.addNodeWithUUID(sitemapsNode, clonedSiteNode, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps"); 255 } 256 else 257 { 258 clonedSitemapsNode = clonedSiteNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":sitemaps"); 259 } 260 261 for (Sitemap sitemap : site.getSitemaps()) 262 { 263 _populate(liveSession, clonedSitemapsNode, sitemap, skin); 264 } 265 } 266 267 /** 268 * Populates a sitemap. 269 * @param liveSession The live session 270 * @param clonedSitemapsNode the cloned sitemaps node. 271 * @param sitemap the sitemap to populate. 272 * @param skin the site skin model 273 * @throws RepositoryException if an error occurs. 274 */ 275 protected void _populate(Session liveSession, Node clonedSitemapsNode, Sitemap sitemap, Skin skin) throws RepositoryException 276 { 277 if (getLogger().isDebugEnabled()) 278 { 279 getLogger().debug("Creating sitemap: " + sitemap); 280 } 281 282 Node sitemapNode = sitemap.getNode(); 283 Node clonedSitemapNode = _synchronizeComponent.addNodeWithUUID(sitemapNode, clonedSitemapsNode, sitemapNode.getName()); 284 285 _synchronizeComponent.synchronizeACL(sitemapNode, liveSession); 286 287 // Clone only properties 288 _synchronizeComponent.cloneNodeAndPreserveUUID(sitemapNode, clonedSitemapNode, PredicateUtils.truePredicate(), PredicateUtils.falsePredicate()); 289 290 // Clone zones 291 _synchronizeComponent.cloneZones(sitemapNode, sitemap, clonedSitemapNode); 292 293 AmetysObjectIterable<? extends Page> pageIterable = sitemap.getChildrenPages(); 294 295 // Clone eligible pages 296 for (Page page : pageIterable) 297 { 298 if (_synchronizeComponent.isPageValid(page, skin)) 299 { 300 _synchronizeComponent.cloneEligiblePage(page, skin, clonedSitemapsNode.getSession()); 301 } 302 } 303 } 304}