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