View Javadoc

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 }