1 /* 2 * ModeShape (http://www.modeshape.org) 3 * See the COPYRIGHT.txt file distributed with this work for information 4 * regarding copyright ownership. Some portions may be licensed 5 * to Red Hat, Inc. under one or more contributor license agreements. 6 * See the AUTHORS.txt file in the distribution for a full listing of 7 * individual contributors. 8 * 9 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape 10 * is licensed to you under the terms of the GNU Lesser General Public License as 11 * published by the Free Software Foundation; either version 2.1 of 12 * the License, or (at your option) any later version. 13 * 14 * ModeShape is distributed in the hope that it will be useful, 15 * but WITHOUT ANY WARRANTY; without even the implied warranty of 16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 17 * Lesser General Public License for more details. 18 * 19 * You should have received a copy of the GNU Lesser General Public 20 * License along with this software; if not, write to the Free 21 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 22 * 02110-1301 USA, or see the FSF site: http://www.fsf.org. 23 */ 24 package org.modeshape.graph.request; 25 26 import java.util.Collections; 27 import java.util.HashSet; 28 import java.util.Map; 29 import java.util.Set; 30 import org.modeshape.common.util.CheckArg; 31 import org.modeshape.common.util.HashCode; 32 import org.modeshape.graph.GraphI18n; 33 import org.modeshape.graph.Location; 34 import org.modeshape.graph.property.Name; 35 import org.modeshape.graph.property.Path; 36 import org.modeshape.graph.property.Property; 37 38 /** 39 * Instruction to update the properties on the node at the specified location. 40 * <p> 41 * This request is capable of specifying that certain properties are to have new values and that other properties are to be 42 * removed. The request has a single map of properties keyed by their name. If a property is to be set with new values, the map 43 * will contain an entry with the property keyed by its name. However, if a property is to be removed, the entry will contain the 44 * property name for the key but will have a null entry value. 45 * </p> 46 * <p> 47 * The use of the map also ensures that a single property appears only once in the request (it either has new values or it is to 48 * be removed). 49 * </p> 50 * <p> 51 * Note that the number of values in a property (e.g., {@link Property#size()}, {@link Property#isEmpty()}, 52 * {@link Property#isSingle()}, and {@link Property#isMultiple()}) has no influence on whether the property should be removed. It 53 * is possible for a property to have no values. 54 * </p> 55 */ 56 public class UpdatePropertiesRequest extends ChangeRequest { 57 58 private static final long serialVersionUID = 1L; 59 60 private final Location on; 61 private final String workspaceName; 62 private final Map<Name, Property> properties; 63 private final boolean removeOtherProperties; 64 private Set<Name> createdPropertyNames; 65 private Location actualLocation; 66 67 /** 68 * Create a request to update the properties on the node at the supplied location. 69 * 70 * @param on the location of the node to be read 71 * @param workspaceName the name of the workspace containing the node 72 * @param properties the map of properties (keyed by their name), which is reused without copying 73 * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to update 74 */ 75 public UpdatePropertiesRequest( Location on, 76 String workspaceName, 77 Map<Name, Property> properties ) { 78 this(on, workspaceName, properties, false); 79 } 80 81 /** 82 * Create a request to update the properties on the node at the supplied location. 83 * 84 * @param on the location of the node to be read 85 * @param workspaceName the name of the workspace containing the node 86 * @param properties the map of properties (keyed by their name), which is reused without copying 87 * @param removeOtherProperties if any properties not being updated should be removed 88 * @throws IllegalArgumentException if the location or workspace name is null or if there are no properties to update 89 */ 90 public UpdatePropertiesRequest( Location on, 91 String workspaceName, 92 Map<Name, Property> properties, 93 boolean removeOtherProperties ) { 94 CheckArg.isNotNull(on, "on"); 95 CheckArg.isNotEmpty(properties, "properties"); 96 CheckArg.isNotNull(workspaceName, "workspaceName"); 97 this.workspaceName = workspaceName; 98 this.on = on; 99 this.properties = Collections.unmodifiableMap(properties); 100 this.removeOtherProperties = removeOtherProperties; 101 } 102 103 /** 104 * {@inheritDoc} 105 * 106 * @see org.modeshape.graph.request.Request#isReadOnly() 107 */ 108 @Override 109 public boolean isReadOnly() { 110 return false; 111 } 112 113 /** 114 * Get the location defining the node that is to be updated. 115 * 116 * @return the location of the node; never null 117 */ 118 public Location on() { 119 return on; 120 } 121 122 /** 123 * Get the name of the workspace in which the node exists. 124 * 125 * @return the name of the workspace; never null 126 */ 127 public String inWorkspace() { 128 return workspaceName; 129 } 130 131 /** 132 * Get the map of properties for the node, keyed by property name. Any property to be removed will have a map entry with a 133 * null value. 134 * 135 * @return the properties being updated; never null and never empty 136 */ 137 public Map<Name, Property> properties() { 138 return properties; 139 } 140 141 /** 142 * Return whether any properties not being updated should be removed. 143 * 144 * @return true if the node's existing properties not updated with this request should be removed, or false if this request 145 * should leave other properties unchanged 146 */ 147 public boolean removeOtherProperties() { 148 return removeOtherProperties; 149 } 150 151 /** 152 * Sets the actual and complete location of the node being updated. This method must be called when processing the request, 153 * and the actual location must have a {@link Location#getPath() path}. 154 * 155 * @param actual the actual location of the node being updated, or null if the {@link #on() current location} should be used 156 * @throws IllegalArgumentException if the actual location is null or does not have a path. 157 * @throws IllegalStateException if the request is frozen 158 * @see #setNewProperties(Iterable) 159 * @see #setNewProperties(Name...) 160 * @see #setNewProperty(Name) 161 */ 162 public void setActualLocationOfNode( Location actual ) { 163 checkNotFrozen(); 164 CheckArg.isNotNull(actual, "actual"); 165 if (!actual.hasPath()) { 166 throw new IllegalArgumentException(GraphI18n.actualLocationMustHavePath.text(actual)); 167 } 168 this.actualLocation = actual; 169 } 170 171 /** 172 * Get the actual location of the node that was updated. 173 * 174 * @return the actual location, or null if the actual location was not set 175 */ 176 public Location getActualLocationOfNode() { 177 return actualLocation; 178 } 179 180 /** 181 * Record that the named property did not exist prior to the processing of this request and was actually created by this 182 * request. This method (or one of its sibling methods) must be called at least once when processing the request, and may be 183 * called repeatedly for additional properties. 184 * 185 * @param nameOfCreatedProperty the name of one of the {@link #properties() properties} that was created by this request 186 * @throws IllegalStateException if the request is frozen 187 * @throws IllegalArgumentException if the name is null or if it is not the name of one of the {@link #properties() 188 * properties} 189 * @see #setActualLocationOfNode(Location) 190 * @see #setNewProperties(Name...) 191 * @see #setNewProperties(Iterable) 192 */ 193 public void setNewProperty( Name nameOfCreatedProperty ) { 194 CheckArg.isNotNull(nameOfCreatedProperty, "nameOfCreatedProperty"); 195 checkNotFrozen(); 196 if (!properties().containsKey(nameOfCreatedProperty)) { 197 throw new IllegalStateException(GraphI18n.propertyIsNotPartOfRequest.text(nameOfCreatedProperty, this)); 198 } 199 if (createdPropertyNames == null) createdPropertyNames = new HashSet<Name>(); 200 createdPropertyNames.add(nameOfCreatedProperty); 201 } 202 203 /** 204 * Record that the named properties did not exist prior to the processing of this request and were actually created by this 205 * request. This method (or one of its sibling methods) must be called at least once when processing the request, and may be 206 * called repeatedly for additional properties. 207 * 208 * @param nameOfCreatedProperties the names of the {@link #properties() properties} that were created by this request 209 * @throws IllegalStateException if the request is frozen 210 * @throws IllegalArgumentException if the name is null or if it is not the name of one of the {@link #properties() 211 * properties} 212 * @see #setActualLocationOfNode(Location) 213 * @see #setNewProperties(Iterable) 214 * @see #setNewProperty(Name) 215 */ 216 public void setNewProperties( Name... nameOfCreatedProperties ) { 217 checkNotFrozen(); 218 if (createdPropertyNames == null) createdPropertyNames = new HashSet<Name>(); 219 for (Name name : nameOfCreatedProperties) { 220 if (name != null) { 221 if (!properties().containsKey(name)) { 222 throw new IllegalStateException(GraphI18n.propertyIsNotPartOfRequest.text(name, this)); 223 } 224 createdPropertyNames.add(name); 225 } 226 } 227 } 228 229 /** 230 * Record that the named properties did not exist prior to the processing of this request and were actually created by this 231 * request. This method (or one of its sibling methods) must be called at least once when processing the request, and may be 232 * called repeatedly for additional properties. 233 * 234 * @param nameOfCreatedProperties the names of the {@link #properties() properties} that were created by this request 235 * @throws IllegalStateException if the request is frozen 236 * @throws IllegalArgumentException if any of the names are not in the updated {@link #properties() properties} 237 * @see #setActualLocationOfNode(Location) 238 * @see #setNewProperties(Name...) 239 * @see #setNewProperty(Name) 240 */ 241 public void setNewProperties( Iterable<Name> nameOfCreatedProperties ) { 242 checkNotFrozen(); 243 if (nameOfCreatedProperties == null) return; 244 if (createdPropertyNames == null) createdPropertyNames = new HashSet<Name>(); 245 for (Name name : nameOfCreatedProperties) { 246 if (name != null) { 247 if (!properties().containsKey(name)) { 248 throw new IllegalStateException(GraphI18n.propertyIsNotPartOfRequest.text(name, this)); 249 } 250 createdPropertyNames.add(name); 251 } 252 } 253 } 254 255 /** 256 * Get the names of the {@link #properties() properties} that were created by this request. 257 * 258 * @return the names of the properties 259 */ 260 public Set<Name> getNewPropertyNames() { 261 return createdPropertyNames; 262 } 263 264 /** 265 * Determine whether the named property was created by this request 266 * 267 * @param name the property name 268 * @return true if the named property was created by the request, or false otherwise 269 */ 270 public boolean isNewProperty( Name name ) { 271 return createdPropertyNames != null && createdPropertyNames.contains(name); 272 } 273 274 /** 275 * Determine whether this request only added the properties. 276 * 277 * @return true if the properties being updated were all new properties, or false otherwise 278 */ 279 public boolean isAllNewProperties() { 280 if (createdPropertyNames != null && createdPropertyNames.containsAll(properties.values())) return true; 281 return false; 282 } 283 284 /** 285 * {@inheritDoc} 286 * 287 * @see org.modeshape.graph.request.Request#freeze() 288 */ 289 @Override 290 public boolean freeze() { 291 if (super.freeze()) { 292 if (createdPropertyNames != null) { 293 if (createdPropertyNames.isEmpty()) { 294 createdPropertyNames = Collections.emptySet(); 295 } else if (createdPropertyNames.size() == 1) { 296 createdPropertyNames = Collections.singleton(createdPropertyNames.iterator().next()); 297 } else { 298 createdPropertyNames = Collections.unmodifiableSet(createdPropertyNames); 299 } 300 } 301 return true; 302 } 303 return false; 304 } 305 306 /** 307 * {@inheritDoc} 308 * 309 * @see org.modeshape.graph.request.ChangeRequest#changes(java.lang.String, org.modeshape.graph.property.Path) 310 */ 311 @Override 312 public boolean changes( String workspace, 313 Path path ) { 314 return this.workspaceName.equals(workspace) && on.hasPath() && on.getPath().isAtOrBelow(path); 315 } 316 317 /** 318 * {@inheritDoc} 319 * 320 * @see org.modeshape.graph.request.Request#cancel() 321 */ 322 @Override 323 public void cancel() { 324 super.cancel(); 325 this.actualLocation = null; 326 } 327 328 /** 329 * {@inheritDoc} 330 * 331 * @see java.lang.Object#hashCode() 332 */ 333 @Override 334 public int hashCode() { 335 return HashCode.compute(on, workspaceName); 336 } 337 338 /** 339 * {@inheritDoc} 340 * 341 * @see java.lang.Object#equals(java.lang.Object) 342 */ 343 @Override 344 public boolean equals( Object obj ) { 345 if (obj == this) return true; 346 if (this.getClass().isInstance(obj)) { 347 UpdatePropertiesRequest that = (UpdatePropertiesRequest)obj; 348 if (!this.on().isSame(that.on())) return false; 349 if (!this.properties().equals(that.properties())) return false; 350 if (!this.inWorkspace().equals(that.inWorkspace())) return false; 351 return true; 352 } 353 return false; 354 } 355 356 /** 357 * {@inheritDoc} 358 * 359 * @see org.modeshape.graph.request.ChangeRequest#changedLocation() 360 */ 361 @Override 362 public Location changedLocation() { 363 return actualLocation != null ? actualLocation : on; 364 } 365 366 /** 367 * {@inheritDoc} 368 * 369 * @see org.modeshape.graph.request.ChangeRequest#changedWorkspace() 370 */ 371 @Override 372 public String changedWorkspace() { 373 return workspaceName; 374 } 375 376 /** 377 * {@inheritDoc} 378 * 379 * @see java.lang.Object#toString() 380 */ 381 @Override 382 public String toString() { 383 if (removeOtherProperties) { 384 return "update (and remove other) properties on " + on() + " in the \"" + workspaceName + "\" workspace to " 385 + properties(); 386 } 387 return "update properties on " + on() + " in the \"" + workspaceName + "\" workspace to " + properties(); 388 } 389 390 /** 391 * {@inheritDoc} 392 * <p> 393 * This method does not clone the results. 394 * </p> 395 * 396 * @see org.modeshape.graph.request.ChangeRequest#clone() 397 */ 398 @Override 399 public UpdatePropertiesRequest clone() { 400 UpdatePropertiesRequest request = new UpdatePropertiesRequest(actualLocation != null ? actualLocation : on, 401 workspaceName, properties, removeOtherProperties); 402 request.setActualLocationOfNode(actualLocation); 403 request.setNewProperties(createdPropertyNames); 404 return request; 405 } 406 407 @Override 408 public RequestType getType() { 409 return RequestType.UPDATE_PROPERTIES; 410 } 411 }