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 }