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        AmetysObjectIterable<? extends Page> pageIterable = sitemap.getChildrenPages();
291        
292        // Clone eligible pages
293        for (Page page : pageIterable)
294        {
295            if (_synchronizeComponent.isPageValid(page, skin))
296            {
297                _synchronizeComponent.cloneEligiblePage(page, skin, clonedSitemapsNode.getSession());
298            }
299        }
300    }
301}