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.graph.observe; 025 026 import java.util.Collections; 027 import java.util.EnumSet; 028 import java.util.HashMap; 029 import java.util.HashSet; 030 import java.util.Map; 031 import java.util.Set; 032 import java.util.TreeMap; 033 import net.jcip.annotations.Immutable; 034 import net.jcip.annotations.NotThreadSafe; 035 import org.jboss.dna.common.util.HashCode; 036 import org.jboss.dna.graph.Location; 037 import org.jboss.dna.graph.property.Name; 038 import org.jboss.dna.graph.property.Path; 039 import org.jboss.dna.graph.property.Property; 040 import org.jboss.dna.graph.request.ChangeRequest; 041 import org.jboss.dna.graph.request.CreateNodeRequest; 042 import org.jboss.dna.graph.request.DeleteBranchRequest; 043 import org.jboss.dna.graph.request.DeleteChildrenRequest; 044 import org.jboss.dna.graph.request.RemovePropertyRequest; 045 import org.jboss.dna.graph.request.SetPropertyRequest; 046 import org.jboss.dna.graph.request.UpdatePropertiesRequest; 047 048 /** 049 * A specialized {@link Observer} that figures out the net changes made during a single {@link Changes set of changes}. For 050 * example, if a property is updated and then updated again, the net change will be a single change. Or, if a node is created and 051 * then deleted, no net change will be observed. 052 */ 053 public abstract class NetChangeObserver extends ChangeObserver { 054 055 public enum ChangeType { 056 NODE_ADDED, 057 NODE_REMOVED, 058 PROPERTY_ADDED, 059 PROPERTY_REMOVED, 060 PROPERTY_CHANGED; 061 } 062 063 protected NetChangeObserver() { 064 } 065 066 /** 067 * {@inheritDoc} 068 * 069 * @see org.jboss.dna.graph.observe.ChangeObserver#notify(org.jboss.dna.graph.observe.Changes) 070 */ 071 @Override 072 public void notify( Changes changes ) { 073 Map<String, Map<Location, NetChangeDetails>> detailsByLocationByWorkspace = new HashMap<String, Map<Location, NetChangeDetails>>(); 074 // Process each of the events, extracting the node path and property details for each ... 075 for (ChangeRequest change : changes) { 076 Location location = change.changedLocation(); 077 String workspace = change.changedWorkspace(); 078 079 // Find the NetChangeDetails for this node ... 080 Map<Location, NetChangeDetails> detailsByLocation = detailsByLocationByWorkspace.get(workspace); 081 NetChangeDetails details = null; 082 if (detailsByLocation == null) { 083 detailsByLocation = new TreeMap<Location, NetChangeDetails>(); 084 detailsByLocationByWorkspace.put(workspace, detailsByLocation); 085 details = new NetChangeDetails(); 086 detailsByLocation.put(location, details); 087 } else { 088 details = detailsByLocation.get(location); 089 if (details == null) { 090 details = new NetChangeDetails(); 091 detailsByLocation.put(location, details); 092 } 093 } 094 095 // Process the specific kind of change ... 096 if (change instanceof CreateNodeRequest) { 097 CreateNodeRequest create = (CreateNodeRequest)change; 098 details.addEventType(ChangeType.NODE_ADDED); 099 for (Property property : create) { 100 details.addProperty(property); 101 } 102 } else if (change instanceof UpdatePropertiesRequest) { 103 UpdatePropertiesRequest update = (UpdatePropertiesRequest)change; 104 for (Map.Entry<Name, Property> entry : update.properties().entrySet()) { 105 Property property = entry.getValue(); 106 if (property != null) { 107 details.changeProperty(property); 108 } else { 109 details.removeProperty(entry.getKey()); 110 } 111 } 112 } else if (change instanceof SetPropertyRequest) { 113 SetPropertyRequest set = (SetPropertyRequest)change; 114 details.changeProperty(set.property()); 115 } else if (change instanceof RemovePropertyRequest) { 116 RemovePropertyRequest remove = (RemovePropertyRequest)change; 117 details.removeProperty(remove.propertyName()); 118 } else if (change instanceof DeleteBranchRequest) { 119 details.addEventType(ChangeType.NODE_REMOVED); 120 } else if (change instanceof DeleteChildrenRequest) { 121 DeleteChildrenRequest delete = (DeleteChildrenRequest)change; 122 for (Location deletedChild : delete.getActualChildrenDeleted()) { 123 NetChangeDetails childDetails = detailsByLocation.get(location); 124 if (childDetails == null) { 125 childDetails = new NetChangeDetails(); 126 detailsByLocation.put(deletedChild, childDetails); 127 } 128 childDetails.addEventType(ChangeType.NODE_REMOVED); 129 } 130 } 131 } 132 133 // Walk through the net changes ... 134 String sourceName = changes.getSourceName(); 135 for (Map.Entry<String, Map<Location, NetChangeDetails>> byWorkspaceEntry : detailsByLocationByWorkspace.entrySet()) { 136 String workspaceName = byWorkspaceEntry.getKey(); 137 // Iterate over the entries. Since we've used a TreeSet, we'll get these with the lower paths first ... 138 for (Map.Entry<Location, NetChangeDetails> entry : byWorkspaceEntry.getValue().entrySet()) { 139 Location location = entry.getKey(); 140 NetChangeDetails details = entry.getValue(); 141 notify(new NetChange(sourceName, workspaceName, location, details.getEventTypes(), 142 details.getModifiedProperties(), details.getRemovedProperties())); 143 } 144 } 145 } 146 147 /** 148 * Method that is called for each net change. 149 * 150 * @param change 151 */ 152 protected abstract void notify( NetChange change ); 153 154 /** 155 * A notification of changes to a node. 156 */ 157 @Immutable 158 public static class NetChange { 159 160 private final String sourceName; 161 private final String workspaceName; 162 private final Location location; 163 private final EnumSet<ChangeType> eventTypes; 164 private final Set<Property> modifiedProperties; 165 private final Set<Name> removedProperties; 166 private final int hc; 167 168 public NetChange( String sourceName, 169 String workspaceName, 170 Location location, 171 EnumSet<ChangeType> eventTypes, 172 Set<Property> modifiedProperties, 173 Set<Name> removedProperties ) { 174 assert sourceName != null; 175 assert workspaceName != null; 176 assert location != null; 177 this.sourceName = sourceName; 178 this.workspaceName = workspaceName; 179 this.location = location; 180 this.hc = HashCode.compute(this.workspaceName, this.location); 181 this.eventTypes = eventTypes; 182 if (modifiedProperties == null) modifiedProperties = Collections.emptySet(); 183 if (removedProperties == null) removedProperties = Collections.emptySet(); 184 this.modifiedProperties = Collections.unmodifiableSet(modifiedProperties); 185 this.removedProperties = Collections.unmodifiableSet(removedProperties); 186 } 187 188 /** 189 * @return absolutePath 190 */ 191 public Path getPath() { 192 return this.location.getPath(); 193 } 194 195 /** 196 * @return repositorySourceName 197 */ 198 public String getRepositorySourceName() { 199 return this.sourceName; 200 } 201 202 /** 203 * @return repositoryWorkspaceName 204 */ 205 public String getRepositoryWorkspaceName() { 206 return this.workspaceName; 207 } 208 209 /** 210 * @return modifiedProperties 211 */ 212 public Set<Property> getModifiedProperties() { 213 return this.modifiedProperties; 214 } 215 216 /** 217 * @return removedProperties 218 */ 219 public Set<Name> getRemovedProperties() { 220 return this.removedProperties; 221 } 222 223 /** 224 * {@inheritDoc} 225 */ 226 @Override 227 public int hashCode() { 228 return this.hc; 229 } 230 231 public boolean includesAll( ChangeType... jcrEventTypes ) { 232 for (ChangeType jcrEventType : jcrEventTypes) { 233 if (!this.eventTypes.contains(jcrEventType)) return false; 234 } 235 return true; 236 } 237 238 public boolean includes( ChangeType... jcrEventTypes ) { 239 for (ChangeType jcrEventType : jcrEventTypes) { 240 if (this.eventTypes.contains(jcrEventType)) return true; 241 } 242 return false; 243 } 244 245 public boolean is( ChangeType jcrEventTypes ) { 246 return this.eventTypes.contains(jcrEventTypes); 247 } 248 249 public boolean isSameNode( NetChange that ) { 250 if (that == this) return true; 251 if (this.hc != that.hc) return false; 252 if (!this.workspaceName.equals(that.workspaceName)) return false; 253 if (!this.location.equals(that.location)) return false; 254 return true; 255 } 256 257 /** 258 * Determine whether this node change includes the setting of new value(s) for the supplied property. 259 * 260 * @param property the name of the property 261 * @return true if the named property has a new value on this node, or false otherwise 262 */ 263 public boolean isPropertyModified( String property ) { 264 return this.modifiedProperties.contains(property); 265 } 266 267 /** 268 * Determine whether this node change includes the removal of the supplied property. 269 * 270 * @param property the name of the property 271 * @return true if the named property was removed from this node, or false otherwise 272 */ 273 public boolean isPropertyRemoved( String property ) { 274 return this.removedProperties.contains(property); 275 } 276 277 /** 278 * {@inheritDoc} 279 */ 280 @Override 281 public boolean equals( Object obj ) { 282 if (obj == this) return true; 283 if (obj instanceof NetChange) { 284 NetChange that = (NetChange)obj; 285 if (!this.isSameNode(that)) return false; 286 if (this.eventTypes != that.eventTypes) return false; 287 return true; 288 } 289 return false; 290 } 291 292 /** 293 * {@inheritDoc} 294 */ 295 @Override 296 public String toString() { 297 return this.workspaceName + "=>" + this.location; 298 } 299 } 300 301 /** 302 * Internal utility class used in the computation of the net changes. 303 */ 304 @NotThreadSafe 305 private static class NetChangeDetails { 306 307 private final Set<Property> modifiedProperties = new HashSet<Property>(); 308 private final Set<Name> removedProperties = new HashSet<Name>(); 309 private EnumSet<ChangeType> eventTypes = EnumSet.noneOf(ChangeType.class); 310 311 protected NetChangeDetails() { 312 } 313 314 public void addEventType( ChangeType eventType ) { 315 this.eventTypes.add(eventType); 316 } 317 318 public void addProperty( Property property ) { 319 this.modifiedProperties.add(property); 320 this.eventTypes.add(ChangeType.PROPERTY_ADDED); 321 } 322 323 public void changeProperty( Property property ) { 324 this.modifiedProperties.add(property); 325 this.eventTypes.add(ChangeType.PROPERTY_CHANGED); 326 } 327 328 public void removeProperty( Name propertyName ) { 329 this.removedProperties.add(propertyName); 330 this.eventTypes.add(ChangeType.PROPERTY_REMOVED); 331 } 332 333 /** 334 * @return nodeAction 335 */ 336 public EnumSet<ChangeType> getEventTypes() { 337 return this.eventTypes; 338 } 339 340 /** 341 * @return addedProperties 342 */ 343 public Set<Property> getModifiedProperties() { 344 return this.modifiedProperties; 345 } 346 347 /** 348 * @return removedProperties 349 */ 350 public Set<Name> getRemovedProperties() { 351 return this.removedProperties; 352 } 353 } 354 }