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 not {@link Location#equals(Object) equal to} the {@link #on() 157 * current location}, or if the actual location does not have a path. 158 * @throws IllegalStateException if the request is frozen 159 * @see #setNewProperties(Iterable) 160 * @see #setNewProperties(Name...) 161 * @see #setNewProperty(Name) 162 */ 163 public void setActualLocationOfNode( Location actual ) { 164 checkNotFrozen(); 165 if (!on.equals(actual)) { // not same if actual is null 166 throw new IllegalArgumentException(GraphI18n.actualLocationNotEqualToInputLocation.text(actual, on)); 167 } 168 assert actual != null; 169 if (!actual.hasPath()) { 170 throw new IllegalArgumentException(GraphI18n.actualLocationMustHavePath.text(actual)); 171 } 172 this.actualLocation = actual; 173 } 174 175 /** 176 * Get the actual location of the node that was updated. 177 * 178 * @return the actual location, or null if the actual location was not set 179 */ 180 public Location getActualLocationOfNode() { 181 return actualLocation; 182 } 183 184 /** 185 * Record that the named property did not exist prior to the processing of this request and was actually created by this 186 * request. This method (or one of its sibling methods) must be called at least once when processing the request, and may be 187 * called repeatedly for additional properties. 188 * 189 * @param nameOfCreatedProperty the name of one of the {@link #properties() properties} that was created by this request 190 * @throws IllegalStateException if the request is frozen 191 * @throws IllegalArgumentException if the name is null or if it is not the name of one of the {@link #properties() 192 * properties} 193 * @see #setActualLocationOfNode(Location) 194 * @see #setNewProperties(Name...) 195 * @see #setNewProperties(Iterable) 196 */ 197 public void setNewProperty( Name nameOfCreatedProperty ) { 198 CheckArg.isNotNull(nameOfCreatedProperty, "nameOfCreatedProperty"); 199 checkNotFrozen(); 200 if (!properties().containsKey(nameOfCreatedProperty)) { 201 throw new IllegalStateException(GraphI18n.propertyIsNotPartOfRequest.text(nameOfCreatedProperty, this)); 202 } 203 if (createdPropertyNames == null) createdPropertyNames = new HashSet<Name>(); 204 createdPropertyNames.add(nameOfCreatedProperty); 205 } 206 207 /** 208 * Record that the named properties did not exist prior to the processing of this request and were actually created by this 209 * request. This method (or one of its sibling methods) must be called at least once when processing the request, and may be 210 * called repeatedly for additional properties. 211 * 212 * @param nameOfCreatedProperties the names of the {@link #properties() properties} that were created by this request 213 * @throws IllegalStateException if the request is frozen 214 * @throws IllegalArgumentException if the name is null or if it is not the name of one of the {@link #properties() 215 * properties} 216 * @see #setActualLocationOfNode(Location) 217 * @see #setNewProperties(Iterable) 218 * @see #setNewProperty(Name) 219 */ 220 public void setNewProperties( Name... nameOfCreatedProperties ) { 221 checkNotFrozen(); 222 if (createdPropertyNames == null) createdPropertyNames = new HashSet<Name>(); 223 for (Name name : nameOfCreatedProperties) { 224 if (name != null) { 225 if (!properties().containsKey(name)) { 226 throw new IllegalStateException(GraphI18n.propertyIsNotPartOfRequest.text(name, this)); 227 } 228 createdPropertyNames.add(name); 229 } 230 } 231 } 232 233 /** 234 * Record that the named properties did not exist prior to the processing of this request and were actually created by this 235 * request. This method (or one of its sibling methods) must be called at least once when processing the request, and may be 236 * called repeatedly for additional properties. 237 * 238 * @param nameOfCreatedProperties the names of the {@link #properties() properties} that were created by this request 239 * @throws IllegalStateException if the request is frozen 240 * @throws IllegalArgumentException if any of the names are not in the updated {@link #properties() properties} 241 * @see #setActualLocationOfNode(Location) 242 * @see #setNewProperties(Name...) 243 * @see #setNewProperty(Name) 244 */ 245 public void setNewProperties( Iterable<Name> nameOfCreatedProperties ) { 246 checkNotFrozen(); 247 if (nameOfCreatedProperties == null) return; 248 if (createdPropertyNames == null) createdPropertyNames = new HashSet<Name>(); 249 for (Name name : nameOfCreatedProperties) { 250 if (name != null) { 251 if (!properties().containsKey(name)) { 252 throw new IllegalStateException(GraphI18n.propertyIsNotPartOfRequest.text(name, this)); 253 } 254 createdPropertyNames.add(name); 255 } 256 } 257 } 258 259 /** 260 * Get the names of the {@link #properties() properties} that were created by this request. 261 * 262 * @return the names of the properties 263 */ 264 public Set<Name> getNewPropertyNames() { 265 return createdPropertyNames; 266 } 267 268 /** 269 * Determine whether the named property was created by this request 270 * 271 * @param name the property name 272 * @return true if the named property was created by the request, or false otherwise 273 */ 274 public boolean isNewProperty( Name name ) { 275 return createdPropertyNames != null && createdPropertyNames.contains(name); 276 } 277 278 /** 279 * Determine whether this request only added the properties. 280 * 281 * @return true if the properties being updated were all new properties, or false otherwise 282 */ 283 public boolean isAllNewProperties() { 284 if (createdPropertyNames != null && createdPropertyNames.containsAll(properties.values())) return true; 285 return false; 286 } 287 288 /** 289 * {@inheritDoc} 290 * 291 * @see org.modeshape.graph.request.Request#freeze() 292 */ 293 @Override 294 public boolean freeze() { 295 if (super.freeze()) { 296 if (createdPropertyNames != null) { 297 if (createdPropertyNames.isEmpty()) { 298 createdPropertyNames = Collections.emptySet(); 299 } else if (createdPropertyNames.size() == 1) { 300 createdPropertyNames = Collections.singleton(createdPropertyNames.iterator().next()); 301 } else { 302 createdPropertyNames = Collections.unmodifiableSet(createdPropertyNames); 303 } 304 } 305 return true; 306 } 307 return false; 308 } 309 310 /** 311 * {@inheritDoc} 312 * 313 * @see org.modeshape.graph.request.ChangeRequest#changes(java.lang.String, org.modeshape.graph.property.Path) 314 */ 315 @Override 316 public boolean changes( String workspace, 317 Path path ) { 318 return this.workspaceName.equals(workspace) && on.hasPath() && on.getPath().isAtOrBelow(path); 319 } 320 321 /** 322 * {@inheritDoc} 323 * 324 * @see org.modeshape.graph.request.Request#cancel() 325 */ 326 @Override 327 public void cancel() { 328 super.cancel(); 329 this.actualLocation = null; 330 } 331 332 /** 333 * {@inheritDoc} 334 * 335 * @see java.lang.Object#hashCode() 336 */ 337 @Override 338 public int hashCode() { 339 return HashCode.compute(on, workspaceName); 340 } 341 342 /** 343 * {@inheritDoc} 344 * 345 * @see java.lang.Object#equals(java.lang.Object) 346 */ 347 @Override 348 public boolean equals( Object obj ) { 349 if (obj == this) return true; 350 if (this.getClass().isInstance(obj)) { 351 UpdatePropertiesRequest that = (UpdatePropertiesRequest)obj; 352 if (!this.on().isSame(that.on())) return false; 353 if (!this.properties().equals(that.properties())) return false; 354 if (!this.inWorkspace().equals(that.inWorkspace())) return false; 355 return true; 356 } 357 return false; 358 } 359 360 /** 361 * {@inheritDoc} 362 * 363 * @see org.modeshape.graph.request.ChangeRequest#changedLocation() 364 */ 365 @Override 366 public Location changedLocation() { 367 return actualLocation != null ? actualLocation : on; 368 } 369 370 /** 371 * {@inheritDoc} 372 * 373 * @see org.modeshape.graph.request.ChangeRequest#changedWorkspace() 374 */ 375 @Override 376 public String changedWorkspace() { 377 return workspaceName; 378 } 379 380 /** 381 * {@inheritDoc} 382 * 383 * @see java.lang.Object#toString() 384 */ 385 @Override 386 public String toString() { 387 if (removeOtherProperties) { 388 return "update (and remove other) properties on " + on() + " in the \"" + workspaceName + "\" workspace to " 389 + properties(); 390 } 391 return "update properties on " + on() + " in the \"" + workspaceName + "\" workspace to " + properties(); 392 } 393 394 /** 395 * {@inheritDoc} 396 * <p> 397 * This method does not clone the results. 398 * </p> 399 * 400 * @see org.modeshape.graph.request.ChangeRequest#clone() 401 */ 402 @Override 403 public UpdatePropertiesRequest clone() { 404 UpdatePropertiesRequest request = new UpdatePropertiesRequest(actualLocation != null ? actualLocation : on, 405 workspaceName, properties, removeOtherProperties); 406 request.setActualLocationOfNode(actualLocation); 407 request.setNewProperties(createdPropertyNames); 408 return request; 409 } 410 411 @Override 412 public RequestType getType() { 413 return RequestType.UPDATE_PROPERTIES; 414 } 415 }