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 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 }