001    /*
002     * JBoss, Home of Professional Open Source.
003     * Copyright 2008, Red Hat Middleware LLC, and individual contributors
004     * as indicated by the @author tags. See the copyright.txt file in the
005     * distribution for a full listing of individual contributors. 
006     *
007     * This is free software; you can redistribute it and/or modify it
008     * under the terms of the GNU Lesser General Public License as
009     * published by the Free Software Foundation; either version 2.1 of
010     * the License, or (at your option) any later version.
011     *
012     * This software is distributed in the hope that it will be useful,
013     * but WITHOUT ANY WARRANTY; without even the implied warranty of
014     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015     * Lesser General Public License for more details.
016     *
017     * You should have received a copy of the GNU Lesser General Public
018     * License along with this software; if not, write to the Free
019     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021     */
022    package org.jboss.dna.repository.observation;
023    
024    import java.util.ArrayList;
025    import java.util.Collections;
026    import java.util.Comparator;
027    import java.util.HashMap;
028    import java.util.HashSet;
029    import java.util.Iterator;
030    import java.util.List;
031    import java.util.Map;
032    import java.util.Set;
033    import javax.jcr.RepositoryException;
034    import javax.jcr.observation.Event;
035    import org.jboss.dna.common.i18n.I18n;
036    import org.jboss.dna.common.util.Logger;
037    import org.jboss.dna.repository.RepositoryI18n;
038    
039    /**
040     * A utility class that builds node changes from a sequence of events.
041     * @author Randall Hauch
042     */
043    public class NodeChanges implements Iterable<NodeChange> {
044    
045        public static NodeChanges create( final String repositoryWorkspaceName, Iterable<Event> events ) throws RepositoryException {
046            Map<String, NodeChangeDetails> detailsByNodePath = new HashMap<String, NodeChangeDetails>();
047            // Process each of the events, extracting the node path and property details for each ...
048            for (Event event : events) {
049                final int eventType = event.getType();
050                final String eventPath = event.getPath();
051                if (eventType == Event.PROPERTY_ADDED || eventType == Event.PROPERTY_CHANGED || eventType == Event.PROPERTY_REMOVED) {
052                    // Extract the node's path and property name from the even path ...
053                    int lastDelim = eventPath.lastIndexOf('/');
054                    if (lastDelim < 1 || lastDelim == (eventPath.length() - 1)) {
055                        // The last delimiter doesn't exist, is the first character, or is the last character...
056                        I18n msg =
057                            eventType == Event.PROPERTY_ADDED ? RepositoryI18n.errorFindingPropertyNameInPropertyAddedEvent : eventType == Event.PROPERTY_CHANGED ? RepositoryI18n.errorFindingPropertyNameInPropertyChangedEvent : RepositoryI18n.errorFindingPropertyNameInPropertyRemovedEvent;
058                        Logger.getLogger(NodeChanges.class).error(msg, eventPath);
059                        continue;
060                    }
061                    String nodePath = eventPath.substring(0, lastDelim); // excludes the last delim
062                    String propertyName = eventPath.substring(lastDelim + 1);
063                    // Record the details ...
064                    NodeChangeDetails details = detailsByNodePath.get(nodePath);
065                    if (details == null) {
066                        details = new NodeChangeDetails(nodePath);
067                        detailsByNodePath.put(nodePath, details);
068                    }
069                    switch (eventType) {
070                        case Event.PROPERTY_ADDED: {
071                            details.addProperty(propertyName);
072                            break;
073                        }
074                        case Event.PROPERTY_CHANGED: {
075                            details.changeProperty(propertyName);
076                            break;
077                        }
078                        case Event.PROPERTY_REMOVED: {
079                            details.removeProperty(propertyName);
080                            break;
081                        }
082                    }
083                } else if (eventType == Event.NODE_ADDED || eventType == Event.NODE_REMOVED) {
084                    // Remove the last delimiter if it appears at the end of the path ...
085                    String nodePath = eventPath;
086                    if (nodePath.length() > 1 && nodePath.charAt(nodePath.length() - 1) == '/') {
087                        nodePath = nodePath.substring(0, nodePath.length() - 1);
088                    }
089                    // Record the details ...
090                    NodeChangeDetails details = detailsByNodePath.get(nodePath);
091                    if (details == null) {
092                        details = new NodeChangeDetails(nodePath);
093                        detailsByNodePath.put(nodePath, details);
094                    }
095                    details.addEventType(eventType);
096                }
097            }
098    
099            // Create the node changes ...
100            List<NodeChange> result = new ArrayList<NodeChange>(detailsByNodePath.size());
101            for (NodeChangeDetails detail : detailsByNodePath.values()) {
102                NodeChange change = new NodeChange(repositoryWorkspaceName, detail.getNodePath(), detail.getEventTypes(), detail.getModifiedProperties(), detail.getRemovedProperties());
103                result.add(change);
104            }
105            return new NodeChanges(result);
106        }
107    
108        protected static class NodeChangeDetails {
109    
110            private final String nodePath;
111            private final Set<String> modifiedProperties = new HashSet<String>();
112            private final Set<String> removedProperties = new HashSet<String>();
113            private int eventTypes;
114    
115            protected NodeChangeDetails( String nodePath ) {
116                this.nodePath = nodePath;
117            }
118    
119            public void addEventType( int eventType ) {
120                this.eventTypes |= eventType;
121            }
122    
123            public void addProperty( String propertyName ) {
124                this.modifiedProperties.add(propertyName);
125                this.eventTypes |= Event.PROPERTY_ADDED;
126            }
127    
128            public void changeProperty( String propertyName ) {
129                this.modifiedProperties.add(propertyName);
130                this.eventTypes |= Event.PROPERTY_CHANGED;
131            }
132    
133            public void removeProperty( String propertyName ) {
134                this.removedProperties.add(propertyName);
135                this.eventTypes |= Event.PROPERTY_REMOVED;
136            }
137    
138            /**
139             * @return nodeAction
140             */
141            public int getEventTypes() {
142                return this.eventTypes;
143            }
144    
145            /**
146             * @return nodePath
147             */
148            public String getNodePath() {
149                return this.nodePath;
150            }
151    
152            /**
153             * @return addedProperties
154             */
155            public Set<String> getModifiedProperties() {
156                return this.modifiedProperties;
157            }
158    
159            /**
160             * @return removedProperties
161             */
162            public Set<String> getRemovedProperties() {
163                return this.removedProperties;
164            }
165        }
166    
167        protected static final Comparator<NodeChange> PRE_ORDER = new Comparator<NodeChange>() {
168    
169            public int compare( NodeChange change1, NodeChange change2 ) {
170                return change1.getAbsolutePath().compareTo(change2.getAbsolutePath());
171            }
172        };
173    
174        private final List<NodeChange> changesInPreOrder;
175    
176        protected NodeChanges( List<NodeChange> changes ) {
177            this.changesInPreOrder = Collections.unmodifiableList(changes);
178        }
179    
180        /**
181         * {@inheritDoc}
182         */
183        public Iterator<NodeChange> iterator() {
184            return this.changesInPreOrder.iterator();
185        }
186    
187        public Iterator<NodeChange> getPreOrder() {
188            return this.changesInPreOrder.iterator();
189        }
190    
191        public int size() {
192            return this.changesInPreOrder.size();
193        }
194    
195    }