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 (at 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 org.jboss.dna.common.util.CheckArg;
027    import org.jboss.dna.common.util.HashCode;
028    import org.jboss.dna.graph.GraphI18n;
029    import org.jboss.dna.graph.Location;
030    import org.jboss.dna.graph.NodeConflictBehavior;
031    import org.jboss.dna.graph.property.Name;
032    import org.jboss.dna.graph.property.Path;
033    
034    /**
035     * Instruction that a branch be copied from one location into another. This request can copy a branch in one workspace into
036     * another workspace, or it can copy a branch in the same workspace.
037     * 
038     * @author Randall Hauch
039     */
040    public class CopyBranchRequest extends ChangeRequest {
041    
042        private static final long serialVersionUID = 1L;
043    
044        public static final NodeConflictBehavior DEFAULT_CONFLICT_BEHAVIOR = NodeConflictBehavior.APPEND;
045    
046        private final Location from;
047        private final Location into;
048        private final String fromWorkspace;
049        private final String intoWorkspace;
050        private final Name desiredNameForCopy;
051        private final NodeConflictBehavior conflictBehavior;
052        private Location actualFromLocation;
053        private Location actualIntoLocation;
054    
055        /**
056         * Create a request to copy a branch to another.
057         * 
058         * @param from the location of the top node in the existing branch that is to be copied
059         * @param fromWorkspace the name of the workspace where the <code>from</code> node exists
060         * @param into the location of the existing node into which the copy should be placed
061         * @param intoWorkspace the name of the workspace where the <code>into</code> node is to be copied, or null if the source's
062         *        default workspace is to be used
063         * @throws IllegalArgumentException if any of the parameters are null
064         */
065        public CopyBranchRequest( Location from,
066                                  String fromWorkspace,
067                                  Location into,
068                                  String intoWorkspace ) {
069            this(from, fromWorkspace, into, intoWorkspace, null, DEFAULT_CONFLICT_BEHAVIOR);
070        }
071    
072        /**
073         * Create a request to copy a branch to another.
074         * 
075         * @param from the location of the top node in the existing branch that is to be copied
076         * @param fromWorkspace the name of the workspace where the <code>from</code> node exists
077         * @param into the location of the existing node into which the copy should be placed
078         * @param intoWorkspace the name of the workspace where the <code>into</code> node is to be copied
079         * @param nameForCopy the desired name for the node that results from the copy, or null if the name of the original should be
080         *        used
081         * @throws IllegalArgumentException if any of the parameters are null
082         */
083        public CopyBranchRequest( Location from,
084                                  String fromWorkspace,
085                                  Location into,
086                                  String intoWorkspace,
087                                  Name nameForCopy ) {
088            this(from, fromWorkspace, into, intoWorkspace, nameForCopy, DEFAULT_CONFLICT_BEHAVIOR);
089        }
090    
091        /**
092         * Create a request to copy a branch to another.
093         * 
094         * @param from the location of the top node in the existing branch that is to be copied
095         * @param fromWorkspace the name of the workspace where the <code>from</code> node exists
096         * @param into the location of the existing node into which the copy should be placed
097         * @param intoWorkspace the name of the workspace where the <code>into</code> node is to be copied
098         * @param nameForCopy the desired name for the node that results from the copy, or null if the name of the original should be
099         *        used
100         * @param conflictBehavior the expected behavior if an equivalently-named child already exists at the <code>into</code>
101         *        location
102         * @throws IllegalArgumentException if any of the parameters are null
103         */
104        public CopyBranchRequest( Location from,
105                                  String fromWorkspace,
106                                  Location into,
107                                  String intoWorkspace,
108                                  Name nameForCopy,
109                                  NodeConflictBehavior conflictBehavior ) {
110            CheckArg.isNotNull(from, "from");
111            CheckArg.isNotNull(into, "into");
112            CheckArg.isNotNull(fromWorkspace, "fromWorkspace");
113            CheckArg.isNotNull(intoWorkspace, "intoWorkspace");
114            CheckArg.isNotNull(conflictBehavior, "conflictBehavior");
115            this.from = from;
116            this.into = into;
117            this.fromWorkspace = fromWorkspace;
118            this.intoWorkspace = intoWorkspace;
119            this.desiredNameForCopy = nameForCopy;
120            this.conflictBehavior = conflictBehavior;
121        }
122    
123        /**
124         * Get the location defining the top of the branch to be copied
125         * 
126         * @return the from location; never null
127         */
128        public Location from() {
129            return from;
130        }
131    
132        /**
133         * Get the location defining the parent where the new copy is to be placed
134         * 
135         * @return the to location; never null
136         */
137        public Location into() {
138            return into;
139        }
140    
141        /**
142         * Get the name of the workspace containing the branch to be copied.
143         * 
144         * @return the name of the workspace containing the branch to be copied; never null
145         */
146        public String fromWorkspace() {
147            return fromWorkspace;
148        }
149    
150        /**
151         * Get the name of the workspace where the copy is to be placed
152         * 
153         * @return the name of the workspace where the copy is to be placed; never null
154         */
155        public String intoWorkspace() {
156            return intoWorkspace;
157        }
158    
159        /**
160         * Determine whether this copy operation is within the same workspace.
161         * 
162         * @return true if this operation is to be performed within the same workspace, or false if the workspace of the
163         *         {@link #from() original} is different than that of the {@link #into() copy}
164         */
165        public boolean isSameWorkspace() {
166            return fromWorkspace.equals(intoWorkspace);
167        }
168    
169        /**
170         * Get the name of the copy if it is to be different than that of the original.
171         * 
172         * @return the desired name of the copy, or null if the name of the original is to be used
173         */
174        public Name desiredName() {
175            return desiredNameForCopy;
176        }
177    
178        /**
179         * {@inheritDoc}
180         * 
181         * @see org.jboss.dna.graph.request.Request#isReadOnly()
182         */
183        @Override
184        public boolean isReadOnly() {
185            return false;
186        }
187    
188        /**
189         * Get the expected behavior when copying the branch and the {@link #into() destination} already has a node with the same
190         * name.
191         * 
192         * @return the behavior specification
193         */
194        public NodeConflictBehavior conflictBehavior() {
195            return conflictBehavior;
196        }
197    
198        /**
199         * Sets the actual and complete location of the node being renamed and its new location. This method must be called when
200         * processing the request, and the actual location must have a {@link Location#getPath() path}.
201         * 
202         * @param fromLocation the actual location of the node being copied
203         * @param intoLocation the actual location of the new copy of the node
204         * @throws IllegalArgumentException if the either location is null; if the old location does not represent the
205         *         {@link Location#isSame(Location) same location} as the {@link #from() from location}; if the new location does not
206         *         represent the {@link Location#isSame(Location) same location} as the {@link #into() into location}; if the either
207         *         location does not have a path
208         * @throws IllegalStateException if the request is frozen
209         */
210        public void setActualLocations( Location fromLocation,
211                                        Location intoLocation ) {
212            checkNotFrozen();
213            if (!from.isSame(fromLocation)) { // not same if actual is null
214                throw new IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(fromLocation, from));
215            }
216            CheckArg.isNotNull(intoLocation, "intoLocation");
217            assert fromLocation != null;
218            assert intoLocation != null;
219            if (!fromLocation.hasPath()) {
220                throw new IllegalArgumentException(GraphI18n.actualOldLocationMustHavePath.text(fromLocation));
221            }
222            if (!intoLocation.hasPath()) {
223                throw new IllegalArgumentException(GraphI18n.actualNewLocationMustHavePath.text(intoLocation));
224            }
225            // The 'into' should be the parent of the 'newLocation' ...
226            if (into.hasPath() && !intoLocation.getPath().getParent().equals(into.getPath())) {
227                throw new IllegalArgumentException(GraphI18n.actualLocationIsNotChildOfInputLocation.text(intoLocation, into));
228            }
229            this.actualFromLocation = fromLocation;
230            this.actualIntoLocation = intoLocation;
231        }
232    
233        /**
234         * Get the actual location of the node before being copied.
235         * 
236         * @return the actual location of the node before being moved, or null if the actual location was not set
237         */
238        public Location getActualLocationBefore() {
239            return actualFromLocation;
240        }
241    
242        /**
243         * Get the actual location of the node after being copied.
244         * 
245         * @return the actual location of the node after being copied, or null if the actual location was not set
246         */
247        public Location getActualLocationAfter() {
248            return actualIntoLocation;
249        }
250    
251        /**
252         * {@inheritDoc}
253         * 
254         * @see org.jboss.dna.graph.request.ChangeRequest#changes(java.lang.String, org.jboss.dna.graph.property.Path)
255         */
256        @Override
257        public boolean changes( String workspace,
258                                Path path ) {
259            return this.intoWorkspace.equals(workspace) && into.hasPath() && into.getPath().isAtOrBelow(path);
260        }
261    
262        /**
263         * {@inheritDoc}
264         * 
265         * @see org.jboss.dna.graph.request.ChangeRequest#changedLocation()
266         */
267        @Override
268        public Location changedLocation() {
269            return into;
270        }
271    
272        /**
273         * {@inheritDoc}
274         * 
275         * @see org.jboss.dna.graph.request.ChangeRequest#changedWorkspace()
276         */
277        @Override
278        public String changedWorkspace() {
279            return intoWorkspace();
280        }
281    
282        /**
283         * {@inheritDoc}
284         * 
285         * @see java.lang.Object#hashCode()
286         */
287        @Override
288        public int hashCode() {
289            return HashCode.compute(from, fromWorkspace, into, intoWorkspace);
290        }
291    
292        /**
293         * {@inheritDoc}
294         * 
295         * @see org.jboss.dna.graph.request.Request#cancel()
296         */
297        @Override
298        public void cancel() {
299            super.cancel();
300            this.actualFromLocation = null;
301            this.actualIntoLocation = null;
302        }
303    
304        /**
305         * {@inheritDoc}
306         * 
307         * @see java.lang.Object#equals(java.lang.Object)
308         */
309        @Override
310        public boolean equals( Object obj ) {
311            if (obj == this) return true;
312            if (this.getClass().isInstance(obj)) {
313                CopyBranchRequest that = (CopyBranchRequest)obj;
314                if (!this.from().equals(that.from())) return false;
315                if (!this.into().equals(that.into())) return false;
316                if (!this.conflictBehavior().equals(that.conflictBehavior())) return false;
317                if (!this.fromWorkspace.equals(that.fromWorkspace)) return false;
318                if (!this.intoWorkspace.equals(that.intoWorkspace)) return false;
319                return true;
320            }
321            return false;
322        }
323    
324        /**
325         * {@inheritDoc}
326         * 
327         * @see java.lang.Object#toString()
328         */
329        @Override
330        public String toString() {
331            if (fromWorkspace.equals(intoWorkspace)) {
332                if (desiredNameForCopy != null) {
333                    return "copy branch " + from() + " in the \"" + fromWorkspace + "\" workspace into " + into() + " with name "
334                           + desiredNameForCopy;
335                }
336                return "copy branch " + from() + " in the \"" + fromWorkspace + "\" workspace into " + into();
337            }
338            if (desiredNameForCopy != null) {
339                return "copy branch " + from() + " in the \"" + fromWorkspace + "\" workspace into " + into() + " with name "
340                       + desiredNameForCopy + " in the \"" + intoWorkspace + "\" workspace";
341            }
342            return "copy branch " + from() + " in the \"" + fromWorkspace + "\" workspace into " + into() + " in the \""
343                   + intoWorkspace + "\" workspace";
344        }
345    }