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}