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}