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 */
016
017package org.ametys.web.live;
018
019import java.util.Map;
020import java.util.regex.Pattern;
021
022import javax.jcr.ItemExistsException;
023import javax.jcr.Node;
024import javax.jcr.Property;
025import javax.jcr.Repository;
026import javax.jcr.Session;
027import javax.jcr.Value;
028import javax.jcr.observation.Event;
029import javax.jcr.observation.EventIterator;
030import javax.jcr.observation.EventListener;
031
032import org.apache.avalon.framework.logger.AbstractLogEnabled;
033import org.apache.avalon.framework.logger.Logger;
034import org.apache.commons.collections.PredicateUtils;
035
036import org.ametys.plugins.repository.jcr.NodeTypeHelper;
037import org.ametys.web.WebConstants;
038import org.ametys.web.synchronization.SynchronizeComponent;
039
040/**
041 * JCR {@link EventListener} for monitoring changes in the default workspace and reflect then in the live workspace
042 */
043public class LiveWorkspaceListener extends AbstractLogEnabled implements EventListener
044{
045    /** Avalon Role */
046    public static final String ROLE = LiveWorkspaceListener.class.getName();
047    
048    private Repository _repository;
049
050    private SynchronizeComponent _synchroComponent;
051    
052    private Pattern[] _excludedPaths;
053    
054    /**
055     * Constructor
056     * @param repository the JCR Repository
057     * @param synchroHelper the synchronize helper
058     * @param logger logger for traces
059     */
060    public LiveWorkspaceListener(Repository repository, SynchronizeComponent synchroHelper, Logger logger)
061    {
062        this(repository, synchroHelper, logger, null);
063    }
064    
065    /**
066     * Constructor
067     * @param repository the JCR Repository
068     * @param synchroHelper the synchronize helper
069     * @param logger logger for traces
070     * @param excludedPaths The pattern of paths to exclude
071     */
072    public LiveWorkspaceListener(Repository repository, SynchronizeComponent synchroHelper, Logger logger, Pattern[] excludedPaths)
073    {
074        _repository = repository;
075        _synchroComponent = synchroHelper;
076        enableLogging(logger);
077        _excludedPaths = excludedPaths;
078    }
079    
080    @Override
081    public void onEvent(EventIterator events)
082    {
083        Session session = null;
084        Session liveSession = null;
085
086        try
087        {
088            session = _repository.login();
089            liveSession = _repository.login(WebConstants.LIVE_WORKSPACE);
090            
091            while (events.hasNext())
092            {
093                Event event = events.nextEvent();
094                String path = event.getPath();
095                
096                if (_isValidPath(path))
097                {
098                    _switchOnType(session, liveSession, event, path);
099                }
100            }
101            
102            if (liveSession.hasPendingChanges())
103            {
104                liveSession.save();
105            }
106        }
107        catch (Exception e)
108        {
109            getLogger().warn("Unable to synchronize live workspace", e);
110        }
111        finally
112        {
113            if (session != null)
114            {
115                session.logout();
116            }
117            
118            if (liveSession != null)
119            {
120                liveSession.logout();
121            }
122        }
123    }
124    
125    private boolean _isValidPath (String path)
126    {
127        if (_excludedPaths == null || _excludedPaths.length == 0)
128        {
129            return true;
130        }
131        
132        for (Pattern pattern : _excludedPaths)
133        {
134            if (pattern.matcher(path).matches())
135            {
136                return false;
137            }
138        }
139        
140        return true;
141        
142    }
143
144    private void _switchOnType(Session session, Session liveSession, Event event, String path) throws Exception
145    {
146        switch (event.getType())
147        {
148            case Event.NODE_ADDED :
149                Node node = (Node) session.getItem(path);
150                String parentPath = node.getParent().getPath();
151
152                if (liveSession.itemExists(parentPath) && !liveSession.itemExists(path) && !NodeTypeHelper.getNodeTypeName(node).startsWith("oswf:") && !node.isNodeType("ametys:content"))
153                {
154                    // if source node exists and parent dest node also exists, copy the whole sub tree
155                    // do not simply copy the node, as events may be unordered
156                    Node parentNode = (Node) liveSession.getItem(parentPath);
157                    
158                    try
159                    {
160                        Node clonedNode = _synchroComponent.addNodeWithUUID(node, parentNode, node.getName());
161                        _synchroComponent.cloneNodeAndPreserveUUID(node, clonedNode, PredicateUtils.truePredicate(), PredicateUtils.truePredicate());
162                    }
163                    catch (ItemExistsException ex)
164                    {
165                        // in case of moved nodes, we may receive first a "node added" event, then a "node moved" event.
166                        // in that case, the addNodeWithUUID fail. Just ignore it.
167                    }
168                }
169                
170                break;
171            case Event.NODE_MOVED :
172                _nodeMoved(liveSession, event, path);
173                break;
174            case Event.NODE_REMOVED :
175            case Event.PROPERTY_REMOVED :
176                if (liveSession.itemExists(path) && !session.itemExists(path))
177                {
178                    liveSession.getItem(path).remove();
179                }
180                
181                break;
182            case Event.PROPERTY_ADDED :
183            case Event.PROPERTY_CHANGED :
184                Property property = (Property) session.getItem(path);
185                String name = property.getName();
186                String parent = property.getParent().getPath();
187                
188                if (liveSession.itemExists(parent) && !property.getDefinition().isProtected() && !property.getName().startsWith("oswf:"))
189                {
190                    if (property.isMultiple())
191                    {
192                        Value[] values = property.getValues();
193                        ((Node) liveSession.getItem(parent)).setProperty(name, values);
194                    }
195                    else
196                    {
197                        Value value = property.getValue();
198                        ((Node) liveSession.getItem(parent)).setProperty(name, value);
199                    }
200                }
201                
202                break;
203            default :
204                break;
205        }
206    }
207    
208    private void _nodeMoved(Session liveSession, Event event, String path) throws Exception
209    {
210        Map<String, String> info = event.getInfo();
211        String srcChildRelPath = info.get("srcChildRelPath");
212        
213        if (srcChildRelPath != null)
214        {
215            // in case of Node.orderBefore()
216            if (liveSession.nodeExists(path))
217            {
218                Node parentNode = liveSession.getItem(path).getParent();
219                
220                String destChildRelPath = info.get("destChildRelPath");
221                
222                if (parentNode.hasNode(srcChildRelPath))
223                {
224                    parentNode.orderBefore(srcChildRelPath, destChildRelPath);
225                }
226            }
227        }
228        else
229        {
230            // in case of Session.move() or Workspace.move()
231            String srcAbsPath = info.get("srcAbsPath");
232            String destAbsPath = info.get("destAbsPath");
233            if (liveSession.itemExists(srcAbsPath) && !liveSession.itemExists(destAbsPath))
234            {
235                liveSession.move(srcAbsPath, destAbsPath);
236            }
237        }
238    }
239}