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                if (liveSession.nodeExists(path) && !session.nodeExists(path))
176                {
177                    liveSession.getItem(path).remove();
178                }
179                break;
180            case Event.PROPERTY_REMOVED :
181                if (liveSession.propertyExists(path) && !session.propertyExists(path))
182                {
183                    liveSession.getItem(path).remove();
184                }
185                break;
186            case Event.PROPERTY_ADDED :
187            case Event.PROPERTY_CHANGED :
188                Property property = (Property) session.getItem(path);
189                String name = property.getName();
190                String parent = property.getParent().getPath();
191                
192                if (liveSession.itemExists(parent) && !property.getDefinition().isProtected() && !property.getName().startsWith("oswf:"))
193                {
194                    if (property.isMultiple())
195                    {
196                        Value[] values = property.getValues();
197                        ((Node) liveSession.getItem(parent)).setProperty(name, values);
198                    }
199                    else
200                    {
201                        Value value = property.getValue();
202                        ((Node) liveSession.getItem(parent)).setProperty(name, value);
203                    }
204                }
205                
206                break;
207            default :
208                break;
209        }
210    }
211    
212    private void _nodeMoved(Session liveSession, Event event, String path) throws Exception
213    {
214        Map<String, String> info = event.getInfo();
215        String srcChildRelPath = info.get("srcChildRelPath");
216        
217        if (srcChildRelPath != null)
218        {
219            // in case of Node.orderBefore()
220            if (liveSession.nodeExists(path))
221            {
222                Node parentNode = liveSession.getItem(path).getParent();
223                
224                String destChildRelPath = info.get("destChildRelPath");
225                
226                if (parentNode.hasNode(srcChildRelPath))
227                {
228                    parentNode.orderBefore(srcChildRelPath, destChildRelPath);
229                }
230            }
231        }
232        else
233        {
234            // in case of Session.move() or Workspace.move()
235            String srcAbsPath = info.get("srcAbsPath");
236            String destAbsPath = info.get("destAbsPath");
237            if (liveSession.itemExists(srcAbsPath) && !liveSession.itemExists(destAbsPath))
238            {
239                liveSession.move(srcAbsPath, destAbsPath);
240            }
241        }
242    }
243}