001    /*
002     * JBoss DNA (http://www.jboss.org/dna)
003     * See the COPYRIGHT.txt file distributed with this work for information
004     * regarding copyright ownership.  Some portions may be licensed
005     * to Red Hat, Inc. under one or more contributor license agreements.
006     * See the AUTHORS.txt file in the distribution for a full listing of 
007     * individual contributors. 
008     *
009     * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
010     * is licensed to you under the terms of the GNU Lesser General Public License as
011     * published by the Free Software Foundation; either version 2.1 of
012     * the License, or (under your option) any later version.
013     *
014     * JBoss DNA is distributed in the hope that it will be useful,
015     * but WITHOUT ANY WARRANTY; without even the implied warranty of
016     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017     * Lesser General Public License for more details.
018     *
019     * You should have received a copy of the GNU Lesser General Public
020     * License along with this software; if not, write to the Free
021     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
022     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
023     */
024    package org.jboss.dna.graph.request;
025    
026    import java.util.ArrayList;
027    import java.util.Collection;
028    import java.util.Collections;
029    import java.util.Iterator;
030    import java.util.LinkedList;
031    import java.util.List;
032    import org.jboss.dna.common.util.CheckArg;
033    import org.jboss.dna.common.util.HashCode;
034    import org.jboss.dna.graph.GraphI18n;
035    import org.jboss.dna.graph.Location;
036    import org.jboss.dna.graph.NodeConflictBehavior;
037    import org.jboss.dna.graph.property.Name;
038    import org.jboss.dna.graph.property.Path;
039    import org.jboss.dna.graph.property.Property;
040    
041    /**
042     * Instruction to create the node under the specified location. This command will create the node and set the initial properties.
043     * 
044     * @author Randall Hauch
045     */
046    public class CreateNodeRequest extends ChangeRequest implements Iterable<Property> {
047    
048        private static final long serialVersionUID = 1L;
049    
050        public static final NodeConflictBehavior DEFAULT_CONFLICT_BEHAVIOR = NodeConflictBehavior.APPEND;
051    
052        private final Location under;
053        private final String workspaceName;
054        private final Name childName;
055        private final List<Property> properties;
056        private final NodeConflictBehavior conflictBehavior;
057        private Location actualLocation;
058    
059        /**
060         * Create a request to create a node with the given properties under the supplied location.
061         * 
062         * @param parentLocation the location of the existing parent node, under which the new child should be created
063         * @param workspaceName the name of the workspace containing the parent
064         * @param childName the name of the new child to create under the existing parent
065         * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
066         *        properties} for the new node
067         * @throws IllegalArgumentException if the location, workspace name, or child name is null
068         */
069        public CreateNodeRequest( Location parentLocation,
070                                  String workspaceName,
071                                  Name childName,
072                                  Property... properties ) {
073            this(parentLocation, workspaceName, childName, DEFAULT_CONFLICT_BEHAVIOR, properties);
074        }
075    
076        /**
077         * Create a request to create a node with the given properties under the supplied location.
078         * 
079         * @param parentLocation the location of the existing parent node, under which the new child should be created
080         * @param workspaceName the name of the workspace containing the parent
081         * @param childName the name of the new child to create under the existing parent
082         * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
083         *        properties} for the new node
084         * @throws IllegalArgumentException if the location, workspace name, or child name is null
085         */
086        public CreateNodeRequest( Location parentLocation,
087                                  String workspaceName,
088                                  Name childName,
089                                  Iterable<Property> properties ) {
090            this(parentLocation, workspaceName, childName, DEFAULT_CONFLICT_BEHAVIOR, properties);
091        }
092    
093        /**
094         * Create a request to create a node with the given properties under the supplied location.
095         * 
096         * @param parentLocation the location of the existing parent node, under which the new child should be created
097         * @param workspaceName the name of the workspace containing the parent
098         * @param childName the name of the new child to create under the existing parent
099         * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
100         *        properties} for the new node
101         * @throws IllegalArgumentException if the location, workspace name, or child name is null
102         */
103        public CreateNodeRequest( Location parentLocation,
104                                  String workspaceName,
105                                  Name childName,
106                                  Iterator<Property> properties ) {
107            this(parentLocation, workspaceName, childName, DEFAULT_CONFLICT_BEHAVIOR, properties);
108        }
109    
110        /**
111         * Create a request to create a node with the given properties under the supplied location.
112         * 
113         * @param parentLocation the location of the existing parent node, under which the new child should be created
114         * @param workspaceName the name of the workspace containing the parent
115         * @param childName the name of the new child to create under the existing parent
116         * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
117         *        properties} for the new node
118         * @param conflictBehavior the expected behavior if an equivalently-named child already exists under the <code>into</code>
119         *        location
120         * @throws IllegalArgumentException if the location, workspace name, child name, or the conflict behavior is null
121         */
122        public CreateNodeRequest( Location parentLocation,
123                                  String workspaceName,
124                                  Name childName,
125                                  NodeConflictBehavior conflictBehavior,
126                                  Property... properties ) {
127            CheckArg.isNotNull(parentLocation, "parentLocation");
128            CheckArg.isNotNull(workspaceName, "workspaceName");
129            CheckArg.isNotNull(conflictBehavior, "conflictBehavior");
130            CheckArg.isNotNull(childName, "childName");
131            this.under = parentLocation;
132            this.workspaceName = workspaceName;
133            this.childName = childName;
134            this.conflictBehavior = conflictBehavior;
135            int number = properties.length + (under.hasIdProperties() ? under.getIdProperties().size() : 0);
136            List<Property> props = new ArrayList<Property>(number);
137            for (Property property : properties) {
138                if (property != null) props.add(property);
139            }
140            this.properties = Collections.unmodifiableList(props);
141        }
142    
143        /**
144         * Create a request to create a node with the given properties under the supplied location.
145         * 
146         * @param parentLocation the location of the existing parent node, under which the new child should be created
147         * @param workspaceName the name of the workspace containing the parent
148         * @param childName the name of the new child to create under the existing parent
149         * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
150         *        properties} for the new node
151         * @param conflictBehavior the expected behavior if an equivalently-named child already exists under the <code>into</code>
152         *        location
153         * @throws IllegalArgumentException if the location, workspace name, child name, or the conflict behavior is null
154         */
155        public CreateNodeRequest( Location parentLocation,
156                                  String workspaceName,
157                                  Name childName,
158                                  NodeConflictBehavior conflictBehavior,
159                                  Iterable<Property> properties ) {
160            CheckArg.isNotNull(parentLocation, "parentLocation");
161            CheckArg.isNotNull(workspaceName, "workspaceName");
162            CheckArg.isNotNull(conflictBehavior, "conflictBehavior");
163            CheckArg.isNotNull(childName, "childName");
164            this.under = parentLocation;
165            this.workspaceName = workspaceName;
166            this.childName = childName;
167            this.conflictBehavior = conflictBehavior;
168            List<Property> props = new LinkedList<Property>();
169            for (Property property : properties) {
170                if (property != null) props.add(property);
171            }
172            this.properties = Collections.unmodifiableList(props);
173        }
174    
175        /**
176         * Create a request to create a node with the given properties under the supplied location.
177         * 
178         * @param parentLocation the location of the existing parent node, under which the new child should be created
179         * @param workspaceName the name of the workspace containing the parent
180         * @param childName the name of the new child to create under the existing parent
181         * @param properties the properties of the new node, which should include any {@link Location#getIdProperties() identification
182         *        properties} for the new node
183         * @param conflictBehavior the expected behavior if an equivalently-named child already exists under the <code>into</code>
184         *        location
185         * @throws IllegalArgumentException if the location, workspace name, child name, or the conflict behavior is null
186         */
187        public CreateNodeRequest( Location parentLocation,
188                                  String workspaceName,
189                                  Name childName,
190                                  NodeConflictBehavior conflictBehavior,
191                                  Iterator<Property> properties ) {
192            CheckArg.isNotNull(parentLocation, "parentLocation");
193            CheckArg.isNotNull(workspaceName, "workspaceName");
194            CheckArg.isNotNull(conflictBehavior, "conflictBehavior");
195            CheckArg.isNotNull(childName, "childName");
196            this.under = parentLocation;
197            this.workspaceName = workspaceName;
198            this.childName = childName;
199            this.conflictBehavior = conflictBehavior;
200            List<Property> props = new LinkedList<Property>();
201            while (properties.hasNext()) {
202                Property property = properties.next();
203                if (property != null) props.add(property);
204            }
205            this.properties = Collections.unmodifiableList(props);
206        }
207    
208        /**
209         * Get the location defining the parent of the new node that is to be created.
210         * 
211         * @return the location of the parent node; never null
212         */
213        public Location under() {
214            return under;
215        }
216    
217        /**
218         * Get the name of the workspace in which the node is to be createde
219         * 
220         * @return the name of the workspace; never null
221         */
222        public String inWorkspace() {
223            return workspaceName;
224        }
225    
226        /**
227         * Get the name for the new child.
228         * 
229         * @return the child's name; never null
230         */
231        public Name named() {
232            return childName;
233        }
234    
235        /**
236         * {@inheritDoc}
237         * 
238         * @see java.lang.Iterable#iterator()
239         */
240        public Iterator<Property> iterator() {
241            return this.properties.iterator();
242        }
243    
244        /**
245         * Get the properties for the node. If the node's {@link #under() location} has identification properties, the resulting
246         * properties will include the {@link Location#getIdProperties() identification properties}.
247         * 
248         * @return the collection of properties; never null
249         */
250        public Collection<Property> properties() {
251            return properties;
252        }
253    
254        /**
255         * Get the expected behavior when copying the branch and the {@link #under() destination} already has a node with the same
256         * name.
257         * 
258         * @return the behavior specification
259         */
260        public NodeConflictBehavior conflictBehavior() {
261            return conflictBehavior;
262        }
263    
264        /**
265         * {@inheritDoc}
266         * 
267         * @see org.jboss.dna.graph.request.Request#isReadOnly()
268         */
269        @Override
270        public boolean isReadOnly() {
271            return false;
272        }
273    
274        /**
275         * Sets the actual and complete location of the node being created. This method must be called when processing the request,
276         * and the actual location must have a {@link Location#getPath() path}.
277         * 
278         * @param actual the actual location of the node being created, or null if the {@link #under() current location} should be
279         *        used
280         * @throws IllegalArgumentException if the actual location does not represent the {@link Location#isSame(Location) same
281         *         location} as the {@link #under() current location}, or if the actual location does not have a path.
282         * @throws IllegalStateException if the request is frozen
283         */
284        public void setActualLocationOfNode( Location actual ) {
285            checkNotFrozen();
286            CheckArg.isNotNull(actual, "actual");
287            if (!under.isSame(actual, false)) { // not same if actual is null
288            }
289            assert actual != null;
290            if (!actual.hasPath()) {
291                throw new IllegalArgumentException(GraphI18n.actualLocationMustHavePath.text(actual));
292            }
293            assert actual.hasPath();
294            if (under.hasPath() && !under.getPath().equals(actual.getPath().getParent())) {
295                throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(actual, under));
296            }
297            this.actualLocation = actual;
298        }
299    
300        /**
301         * Get the actual location of the node that was created.
302         * 
303         * @return the actual location, or null if the actual location was not set
304         */
305        public Location getActualLocationOfNode() {
306            return actualLocation;
307        }
308    
309        /**
310         * {@inheritDoc}
311         * 
312         * @see org.jboss.dna.graph.request.ChangeRequest#changes(java.lang.String, org.jboss.dna.graph.property.Path)
313         */
314        @Override
315        public boolean changes( String workspace,
316                                Path path ) {
317            return this.workspaceName.equals(workspace) && under.hasPath() && under.getPath().isAtOrBelow(path);
318        }
319    
320        /**
321         * {@inheritDoc}
322         * 
323         * @see org.jboss.dna.graph.request.ChangeRequest#changedLocation()
324         */
325        @Override
326        public Location changedLocation() {
327            return actualLocation;
328        }
329    
330        /**
331         * {@inheritDoc}
332         * 
333         * @see org.jboss.dna.graph.request.ChangeRequest#changedWorkspace()
334         */
335        @Override
336        public String changedWorkspace() {
337            return workspaceName;
338        }
339    
340        /**
341         * {@inheritDoc}
342         * 
343         * @see org.jboss.dna.graph.request.Request#cancel()
344         */
345        @Override
346        public void cancel() {
347            super.cancel();
348            this.actualLocation = null;
349        }
350    
351        /**
352         * {@inheritDoc}
353         * 
354         * @see java.lang.Object#hashCode()
355         */
356        @Override
357        public int hashCode() {
358            return HashCode.compute(under, childName, workspaceName);
359        }
360    
361        /**
362         * {@inheritDoc}
363         * 
364         * @see java.lang.Object#equals(java.lang.Object)
365         */
366        @Override
367        public boolean equals( Object obj ) {
368            if (obj == this) return true;
369            if (this.getClass().isInstance(obj)) {
370                CreateNodeRequest that = (CreateNodeRequest)obj;
371                if (!this.under().equals(that.under())) return false;
372                if (!this.conflictBehavior().equals(that.conflictBehavior())) return false;
373                if (!this.inWorkspace().equals(that.conflictBehavior())) return false;
374                if (!this.properties().equals(that.properties())) return false;
375                return true;
376            }
377            return false;
378        }
379    
380        /**
381         * {@inheritDoc}
382         * 
383         * @see java.lang.Object#toString()
384         */
385        @Override
386        public String toString() {
387            String parent = under() + "/";
388            if (under.hasPath() && under.getPath().isRoot()) parent = "/";
389            return "create in the \"" + workspaceName + "\" workspace the node \"" + parent + childName + "\" with properties "
390                   + properties();
391        }
392    
393    }