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.connector.svn;
025    
026    import java.io.ByteArrayInputStream;
027    import java.io.ByteArrayOutputStream;
028    import java.io.OutputStream;
029    import java.util.Collection;
030    import java.util.Collections;
031    import java.util.Date;
032    import java.util.HashSet;
033    import java.util.Set;
034    import org.jboss.dna.common.i18n.I18n;
035    import org.jboss.dna.common.util.Logger;
036    import org.jboss.dna.connector.scm.ScmAction;
037    import org.jboss.dna.connector.scm.ScmActionFactory;
038    import org.jboss.dna.graph.ExecutionContext;
039    import org.jboss.dna.graph.JcrLexicon;
040    import org.jboss.dna.graph.JcrNtLexicon;
041    import org.jboss.dna.graph.Location;
042    import org.jboss.dna.graph.connector.RepositorySourceException;
043    import org.jboss.dna.graph.property.Binary;
044    import org.jboss.dna.graph.property.DateTimeFactory;
045    import org.jboss.dna.graph.property.Name;
046    import org.jboss.dna.graph.property.NameFactory;
047    import org.jboss.dna.graph.property.Path;
048    import org.jboss.dna.graph.property.PathFactory;
049    import org.jboss.dna.graph.property.PathNotFoundException;
050    import org.jboss.dna.graph.property.Property;
051    import org.jboss.dna.graph.property.PropertyFactory;
052    import org.jboss.dna.graph.property.ValueFactory;
053    import org.jboss.dna.graph.request.CloneWorkspaceRequest;
054    import org.jboss.dna.graph.request.CopyBranchRequest;
055    import org.jboss.dna.graph.request.CreateNodeRequest;
056    import org.jboss.dna.graph.request.CreateWorkspaceRequest;
057    import org.jboss.dna.graph.request.DeleteBranchRequest;
058    import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
059    import org.jboss.dna.graph.request.GetWorkspacesRequest;
060    import org.jboss.dna.graph.request.InvalidRequestException;
061    import org.jboss.dna.graph.request.MoveBranchRequest;
062    import org.jboss.dna.graph.request.ReadAllChildrenRequest;
063    import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
064    import org.jboss.dna.graph.request.RenameNodeRequest;
065    import org.jboss.dna.graph.request.Request;
066    import org.jboss.dna.graph.request.UpdatePropertiesRequest;
067    import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
068    import org.jboss.dna.graph.request.processor.RequestProcessor;
069    import org.tmatesoft.svn.core.SVNDirEntry;
070    import org.tmatesoft.svn.core.SVNErrorCode;
071    import org.tmatesoft.svn.core.SVNErrorMessage;
072    import org.tmatesoft.svn.core.SVNException;
073    import org.tmatesoft.svn.core.SVNNodeKind;
074    import org.tmatesoft.svn.core.SVNProperties;
075    import org.tmatesoft.svn.core.SVNProperty;
076    import org.tmatesoft.svn.core.io.ISVNEditor;
077    import org.tmatesoft.svn.core.io.SVNRepository;
078    import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
079    
080    /**
081     * The {@link RequestProcessor} implementation for the file subversion repository connector. This is the class that does the bulk
082     * of the work in the subversion repository connector, since it processes all requests.
083     * 
084     * @author Serge Emmanuel Pagop
085     */
086    public class SVNRepositoryRequestProcessor extends RequestProcessor implements ScmActionFactory {
087    
088        protected static final String BACK_SLASH = "/";
089    
090        private final String defaultNamespaceUri;
091        private final boolean updatesAllowed;
092        private SVNRepository repository;
093        protected final Logger logger;
094    
095        /**
096         * @param sourceName
097         * @param context
098         * @param repository
099         * @param updatesAllowed true if this connector supports updating the subversion repository, or false if the connector is read
100         *        only
101         */
102        protected SVNRepositoryRequestProcessor( String sourceName,
103                                                 ExecutionContext context,
104                                                 SVNRepository repository,
105                                                 boolean updatesAllowed ) {
106            super(sourceName, context);
107            this.defaultNamespaceUri = getExecutionContext().getNamespaceRegistry().getDefaultNamespaceUri();
108            this.updatesAllowed = updatesAllowed;
109            this.repository = repository;
110            this.logger = getExecutionContext().getLogger(getClass());
111        }
112    
113        /**
114         * {@inheritDoc}
115         * 
116         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CopyBranchRequest)
117         */
118        @Override
119        public void process( CopyBranchRequest request ) {
120            logger.trace(request.toString());
121            verifyUpdatesAllowed();
122    
123        }
124    
125        /**
126         * {@inheritDoc}
127         * 
128         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateNodeRequest)
129         */
130        @Override
131        public void process( CreateNodeRequest request ) {
132            logger.trace(request.toString());
133            verifyUpdatesAllowed();
134            // get the parent location of the new node
135            Location myLocation = request.under();
136            Path parent = getPathFor(myLocation, request);
137            try {
138                String root = parent.getString(getExecutionContext().getNamespaceRegistry());
139                SVNNodeKind rootKind = repository.checkPath(root, -1);
140                if (rootKind == SVNNodeKind.UNKNOWN) {
141                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
142                                                                 "path with name '{0}' is unknown in the repository",
143                                                                 root);
144                    SVNException ex = new SVNException(err);
145                    request.setError(ex);
146                } else if (rootKind == SVNNodeKind.NONE) {
147                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
148                                                                 "path with name '{0}' is missing in the repository",
149                                                                 root);
150                    SVNException ex = new SVNException(err);
151                    request.setError(ex);
152                } else if (rootKind == SVNNodeKind.FILE) {
153                    SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
154                                                                 "pretended root item with name '{0}' is a file",
155                                                                 root);
156                    SVNException ex = new SVNException(err);
157                    request.setError(ex);
158                } else if (rootKind == SVNNodeKind.DIR) {
159                    Collection<Property> childNodeProperties = request.properties();
160                    Object[] objs = values(childNodeProperties);
161                    for (Object object : objs) {
162                        if (object instanceof Name && ((Name)object).compareTo(JcrNtLexicon.FOLDER) == 0) {
163                            // process folder creation
164                            // if the node is a directory
165                            String folderName = request.named().getString(getExecutionContext().getNamespaceRegistry());
166                            if (root.length() == 1 && root.charAt(0) == '/') {
167                                // test if so a directory does not exist.
168                                mkdir("", folderName, request.toString());
169                            } else {
170                                if (root.length() > 1 && root.charAt(0) == '/') {
171                                    // test if so a directory does not exist.
172                                    mkdir(root.substring(1), folderName, request.toString());
173                                }
174                            }
175                        } else if (object instanceof Name && ((Name)object).compareTo(JcrNtLexicon.FILE) == 0) {
176                            String fileName = request.named().getString(getExecutionContext().getNamespaceRegistry());
177                            byte[] content = getContent(objs);
178                            // TODO: what is with the created on
179                            // Date createdOn = getCreatedOn(objs);
180                            // commit in to the repository
181                            newFile(root, fileName, content, request.toString());
182                        }
183                    }
184                }
185    
186            } catch (SVNException e) {
187                request.setError(e);
188            }
189        }
190    
191        /**
192         * {@inheritDoc}
193         * 
194         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DeleteBranchRequest)
195         */
196        @Override
197        public void process( DeleteBranchRequest request ) {
198            logger.trace(request.toString());
199            verifyUpdatesAllowed();
200        }
201    
202        /**
203         * {@inheritDoc}
204         * 
205         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.MoveBranchRequest)
206         */
207        @Override
208        public void process( MoveBranchRequest request ) {
209            logger.trace(request.toString());
210            verifyUpdatesAllowed();
211        }
212    
213        /**
214         * {@inheritDoc}
215         * 
216         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadAllChildrenRequest)
217         */
218        @SuppressWarnings( "unchecked" )
219        @Override
220        public void process( ReadAllChildrenRequest request ) {
221            logger.trace(request.toString());
222            Location myLocation = request.of();
223            Path nodePath = getPathFor(myLocation, request);
224            try {
225                SVNNodeKind kind = validateNodeKind(nodePath);
226                String requestedNodePath = nodePath.getString(getExecutionContext().getNamespaceRegistry());
227                if (kind == SVNNodeKind.FILE) { // the requested node is a file.
228                    SVNDirEntry entry = getEntryInfo(requestedNodePath);
229                    if (!nodePath.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
230                        String localName = entry.getName();
231                        Name childName = nameFactory().create(defaultNamespaceUri, localName);
232                        String url = entry.getURL().toString();
233                        Property idProperty = propertyFactory().create(childName, url);
234                        request.addChild(Location.create(pathFactory().create(nodePath, JcrLexicon.CONTENT), idProperty));
235                    }
236                } else if (kind == SVNNodeKind.DIR) { // the requested node is a directory.
237                    final Collection<SVNDirEntry> dirEntries = getRepository().getDir(requestedNodePath,
238                                                                                      -1,
239                                                                                      null,
240                                                                                      (Collection<SVNDirEntry>)null);
241                    for (SVNDirEntry dirEntry : dirEntries) {
242                        if (dirEntry.getKind() == SVNNodeKind.FILE) {
243                            String localName = dirEntry.getName();
244                            Path newPath = pathFactory().create(requestedNodePath + BACK_SLASH + localName);
245                            if (!newPath.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
246                                Name childName = nameFactory().create(defaultNamespaceUri, localName);
247                                String url = dirEntry.getURL().toString();
248                                Property idProperty = propertyFactory().create(childName, url);
249                                Location location = Location.create(pathFactory().create(newPath, JcrLexicon.CONTENT), idProperty);
250                                request.addChild(location);
251                            }
252                        } else if (dirEntry.getKind() == SVNNodeKind.DIR) {
253                            String localName = dirEntry.getName();
254                            Name childName = nameFactory().create(defaultNamespaceUri, localName);
255                            Path childPath = pathFactory().create(nodePath, childName);
256                            String url = dirEntry.getURL().toString();
257                            Property idProperty = propertyFactory().create(childName, url);
258                            request.addChild(childPath, idProperty);
259                        }
260                    }
261                }
262                request.setActualLocationOfNode(myLocation);
263            } catch (SVNException e) {
264                request.setError(e);
265            }
266    
267        }
268    
269        /**
270         * {@inheritDoc}
271         * 
272         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadAllPropertiesRequest)
273         */
274        @Override
275        public void process( ReadAllPropertiesRequest request ) {
276            logger.trace(request.toString());
277            Location myLocation = request.at();
278            Path nodePath = getPathFor(myLocation, request);
279            if (nodePath.isRoot()) {
280                // There are no properties on the root ...
281                request.setActualLocationOfNode(myLocation);
282                return;
283            }
284            try {
285                // See if the path is a "jcr:content" node ...
286                if (nodePath.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
287                    // //"jcr:primaryType" property value of "nt:resource",
288                    // "jcr:data" property whose value are the contents of the file
289                    // and a few other properties, like "jcr:encoding", "jcr:mimeType" and "jcr:lastModified" and
290                    // also "jcr:created" property
291                    Path parent = nodePath.getParent();
292                    ByteArrayOutputStream os = new ByteArrayOutputStream();
293                    SVNProperties fileProperties = new SVNProperties();
294                    getData(parent.getString(getExecutionContext().getNamespaceRegistry()), fileProperties, os);
295                    Property ntResourceproperty = propertyFactory().create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.RESOURCE);
296                    request.addProperty(ntResourceproperty);
297                    String mimeType = fileProperties.getStringValue(SVNProperty.MIME_TYPE);
298                    if (mimeType != null) {
299                        Property jcrMimeTypeProperty = propertyFactory().create(JcrLexicon.MIMETYPE, mimeType);
300                        request.addProperty(jcrMimeTypeProperty);
301                    }
302                    SVNDirEntry entry = getEntryInfo(parent.getString(getExecutionContext().getNamespaceRegistry()));
303                    Date lastModified = entry.getDate();
304                    if (lastModified != null) {
305                        Property jcrLastModifiedProperty = propertyFactory().create(JcrLexicon.LAST_MODIFIED,
306                                                                                    dateFactory().create(lastModified));
307                        request.addProperty(jcrLastModifiedProperty);
308                    }
309                    if (os.toByteArray().length > 0) {
310                        Property jcrDataProperty = propertyFactory().create(JcrLexicon.DATA, binaryFactory().create(os.toByteArray()));
311                        request.addProperty(jcrDataProperty);
312                    }
313                } else {
314                    SVNNodeKind kind = validateNodeKind(nodePath);
315                    if (kind == SVNNodeKind.FILE) {
316                        // "jcr:primaryType" property whose value is "nt:file".
317                        Property ntFileProperty = propertyFactory().create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FILE);
318                        request.addProperty(ntFileProperty);
319                        ByteArrayOutputStream os = new ByteArrayOutputStream();
320                        SVNProperties fileProperties = new SVNProperties();
321                        getData(nodePath.getString(getExecutionContext().getNamespaceRegistry()), fileProperties, os);
322                        String created = fileProperties.getStringValue(SVNProperty.COMMITTED_DATE);
323                        if (created != null) {
324                            Property jcrCreatedProperty = propertyFactory().create(JcrLexicon.CREATED, created);
325                            request.addProperty(jcrCreatedProperty);
326                        }
327    
328                    } else if (kind == SVNNodeKind.DIR) {
329                        // A directory maps to a single node with a name that represents the name of the directory and a
330                        // "jcr:primaryType" property whose value is "nt:folder"
331                        Property jcrPrimaryTypeProp = propertyFactory().create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FOLDER);
332                        request.addProperty(jcrPrimaryTypeProp);
333                        SVNDirEntry dirEntry = getEntryInfo(nodePath.getString(getExecutionContext().getNamespaceRegistry()));
334                        Property jcrCreatedProp = propertyFactory().create(JcrLexicon.CREATED,
335                                                                           dateFactory().create(dirEntry.getDate()));
336                        request.addProperty(jcrCreatedProp);
337                    }
338                }
339                request.setActualLocationOfNode(myLocation);
340    
341            } catch (SVNException e) {
342                request.setError(e);
343            }
344        }
345    
346        /**
347         * {@inheritDoc}
348         * 
349         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.RenameNodeRequest)
350         */
351        @Override
352        public void process( RenameNodeRequest request ) {
353            logger.trace(request.toString());
354            verifyUpdatesAllowed();
355            super.process(request);
356        }
357    
358        /**
359         * {@inheritDoc}
360         * 
361         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.UpdatePropertiesRequest)
362         */
363        @Override
364        public void process( UpdatePropertiesRequest request ) {
365            logger.trace(request.toString());
366            verifyUpdatesAllowed();
367        }
368    
369        /**
370         * {@inheritDoc}
371         * 
372         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.VerifyWorkspaceRequest)
373         */
374        @Override
375        public void process( VerifyWorkspaceRequest request ) {
376            // This does the job of converting a null workspace name to a valid workspace
377            String workspaceName = request.workspaceName();
378            if (workspaceName == null) workspaceName = "default";
379            request.setActualRootLocation(Location.create(pathFactory().createRootPath()));
380            request.setActualWorkspaceName(workspaceName);
381        }
382    
383        /**
384         * {@inheritDoc}
385         * 
386         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.GetWorkspacesRequest)
387         */
388        @Override
389        public void process( GetWorkspacesRequest request ) {
390            request.setAvailableWorkspaceNames(Collections.singleton("default"));
391        }
392    
393        /**
394         * {@inheritDoc}
395         * 
396         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateWorkspaceRequest)
397         */
398        @Override
399        public void process( CreateWorkspaceRequest request ) {
400            String msg = SVNRepositoryConnectorI18n.sourceDoesNotSupportCreatingWorkspaces.text(getSourceName());
401            request.setError(new InvalidRequestException(msg));
402        }
403    
404        /**
405         * {@inheritDoc}
406         * 
407         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CloneWorkspaceRequest)
408         */
409        @Override
410        public void process( CloneWorkspaceRequest request ) {
411            String msg = SVNRepositoryConnectorI18n.sourceDoesNotSupportCloningWorkspaces.text(getSourceName());
412            request.setError(new InvalidRequestException(msg));
413        }
414    
415        /**
416         * {@inheritDoc}
417         * 
418         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DestroyWorkspaceRequest)
419         */
420        @Override
421        public void process( DestroyWorkspaceRequest request ) {
422            String msg = SVNRepositoryConnectorI18n.sourceDoesNotSupportDeletingWorkspaces.text(getSourceName());
423            request.setError(new InvalidRequestException(msg));
424        }
425    
426        /**
427         * Verify if change is allowed on a specific source.
428         * 
429         * @throws RepositorySourceException if change on that repository source is not allowed.
430         */
431        protected void verifyUpdatesAllowed() {
432            if (!updatesAllowed) {
433                throw new InvalidRequestException(SVNRepositoryConnectorI18n.sourceIsReadOnly.text(getSourceName()));
434            }
435        }
436    
437        /**
438         * Factory for sample name.
439         * 
440         * @return the name factory
441         */
442        protected NameFactory nameFactory() {
443            return getExecutionContext().getValueFactories().getNameFactory();
444        }
445    
446        /**
447         * Factory for path creation.
448         * 
449         * @return a path factory.
450         */
451        protected PathFactory pathFactory() {
452            return getExecutionContext().getValueFactories().getPathFactory();
453        }
454    
455        /**
456         * Factory for property creation.
457         * 
458         * @return the property factory.
459         */
460        protected PropertyFactory propertyFactory() {
461            return getExecutionContext().getPropertyFactory();
462        }
463    
464        /**
465         * Factory for date creation.
466         * 
467         * @return the date factory.
468         */
469        protected DateTimeFactory dateFactory() {
470            return getExecutionContext().getValueFactories().getDateFactory();
471        }
472    
473        /**
474         * Factory for binary creation.
475         * 
476         * @return the binary factory..
477         */
478        protected ValueFactory<Binary> binaryFactory() {
479            return getExecutionContext().getValueFactories().getBinaryFactory();
480        }
481    
482        /**
483         * Get the path for a locarion and check if the path is null or not.
484         * 
485         * @param location - the location.
486         * @param request - the requested path.
487         * @return the path.
488         * @throws RepositorySourceException if the path of a location is null.
489         */
490        protected Path getPathFor( Location location,
491                                   Request request ) {
492            Path path = location.getPath();
493            if (path == null) {
494                I18n msg = SVNRepositoryConnectorI18n.locationInRequestMustHavePath;
495                throw new RepositorySourceException(getSourceName(), msg.text(getSourceName(), request));
496            }
497            return path;
498        }
499    
500        /**
501         * Get the content of a file.
502         * 
503         * @param path - the path to that file.
504         * @param properties - the properties of the file.
505         * @param os - the output stream where to store the content.
506         * @throws SVNException - throws if such path is not at that revision or in case of a connection problem.
507         */
508        protected void getData( String path,
509                                SVNProperties properties,
510                                OutputStream os ) throws SVNException {
511            getRepository().getFile(path, -1, properties, os);
512    
513        }
514    
515        /**
516         * Get the repository driver.
517         * 
518         * @return repository
519         */
520        public SVNRepository getRepository() {
521            return repository;
522        }
523    
524        /**
525         * Validate the kind of node and throws an exception if necessary.
526         * 
527         * @param requestedPath
528         * @return the kind.
529         */
530        protected SVNNodeKind validateNodeKind( final Path requestedPath ) {
531            String myPath = requestedPath.getString(getExecutionContext().getNamespaceRegistry());
532            SVNNodeKind kind = null;
533            try {
534                kind = getRepository().checkPath(myPath, -1);
535                if (kind == SVNNodeKind.NONE) {
536                    // node does not exist or requested node is not correct.
537                    throw new PathNotFoundException(Location.create(requestedPath), null,
538                                                    SVNRepositoryConnectorI18n.nodeDoesNotExist.text(myPath));
539                } else if (kind == SVNNodeKind.UNKNOWN) {
540                    // node is unknown
541                    throw new PathNotFoundException(Location.create(requestedPath), null,
542                                                    SVNRepositoryConnectorI18n.nodeIsActuallyUnknow.text(myPath));
543                }
544            } catch (SVNException e) {
545                throw new RepositorySourceException(
546                                                    getSourceName(),
547                                                    SVNRepositoryConnectorI18n.connectingFailureOrUserAuthenticationProblem.text(getSourceName()));
548            }
549    
550            return kind;
551        }
552    
553        /**
554         * Get some important informations of a path
555         * 
556         * @param path - the path
557         * @return - the {@link SVNDirEntry}.
558         */
559        protected SVNDirEntry getEntryInfo( String path ) {
560            assert path != null;
561            SVNDirEntry entry = null;
562            try {
563                entry = getRepository().info(path, -1);
564            } catch (SVNException e) {
565                throw new RepositorySourceException(
566                                                    getSourceName(),
567                                                    SVNRepositoryConnectorI18n.connectingFailureOrUserAuthenticationProblem.text(getSourceName()));
568            }
569            return entry;
570        }
571    
572        /**
573         * Open the directories where change has to be made.
574         * 
575         * @param editor - abstract editor.
576         * @param rootPath - the pa to open.
577         * @throws SVNException when a error occur.
578         */
579        protected static void openDirectories( ISVNEditor editor,
580                                               String rootPath ) throws SVNException {
581            assert rootPath != null;
582            int pos = rootPath.indexOf('/', 0);
583            while (pos != -1) {
584                String dir = rootPath.substring(0, pos);
585                editor.openDir(dir, -1);
586                pos = rootPath.indexOf('/', pos + 1);
587            }
588            String dir = rootPath.substring(0, rootPath.length());
589            editor.openDir(dir, -1);
590        }
591    
592        /**
593         * Close the directories where change was made.
594         * 
595         * @param editor - the abstract editor.
596         * @param path - the directories to open.
597         * @throws SVNException when a error occur.
598         */
599        protected static void closeDirectories( ISVNEditor editor,
600                                                String path ) throws SVNException {
601            int length = path.length() - 1;
602            int pos = path.lastIndexOf('/', length);
603            editor.closeDir();
604            while (pos != -1) {
605                editor.closeDir();
606                pos = path.lastIndexOf('/', pos - 1);
607            }
608        }
609    
610        /**
611         * Get the last revision.
612         * 
613         * @return the last revision number.
614         * @throws Exception
615         */
616        public long getLatestRevision() throws Exception {
617            try {
618                return repository.getLatestRevision();
619            } catch (SVNException e) {
620                e.printStackTrace();
621                // logger.error( "svn error: " );
622                throw e;
623            }
624        }
625    
626        /**
627         * Add directory in a repository
628         * 
629         * @param repository - the repository.
630         * @param root - the root path has to exist.
631         * @param child - new path to be added.
632         * @param message - information about the change action.
633         * @throws SVNException when a error occur.
634         */
635        protected void addDirEntry( SVNRepository repository,
636                                    String root,
637                                    String child,
638                                    String message ) throws SVNException {
639            assert root.trim().length() != 0;
640            SVNNodeKind rootKind = repository.checkPath(root, -1);
641            if (rootKind == SVNNodeKind.UNKNOWN) {
642                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
643                                                             "path with name '{0}' is unknown in the repository",
644                                                             root);
645                throw new SVNException(err);
646            } else if (rootKind == SVNNodeKind.NONE) {
647                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
648                                                             "path with name '{0}' is missing in the repository",
649                                                             root);
650                throw new SVNException(err);
651            } else if (rootKind == SVNNodeKind.FILE) {
652                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
653                                                             "path with name '{0}' is a file, you need a directory",
654                                                             root);
655                throw new SVNException(err);
656            } else if (rootKind == SVNNodeKind.DIR) {
657                ISVNEditor editor = repository.getCommitEditor(message, null, true, null);
658                if (root.length() == 1 && root.charAt(0) == '/') {
659                    addProcess(editor, root, "", child);
660                } else {
661                    String rootPath = root.substring(1);
662                    addProcess(editor, rootPath, null, child);
663                }
664            }
665        }
666    
667        private void addProcess( ISVNEditor editor,
668                                 String rootPath,
669                                 String editedRoot,
670                                 String childSegmentName ) throws SVNException {
671            openDirectories(editor, editedRoot);
672            // test if so a directory does not exist.
673            SVNNodeKind childKind = repository.checkPath(childSegmentName, -1);
674            if (childKind == SVNNodeKind.NONE) {
675                editor.addDir(childSegmentName, null, -1);
676                closeDirectories(editor, childSegmentName);
677                if (editedRoot != null) {
678                    closeDirectories(editor, editedRoot);
679                } else {
680                    closeDirectories(editor, rootPath);
681                }
682    
683            } else {
684                closeDirectories(editor, childSegmentName);
685                if (editedRoot != null) {
686                    closeDirectories(editor, editedRoot);
687                } else {
688                    closeDirectories(editor, rootPath);
689                }
690            }
691        }
692    
693        /**
694         * Create a directory .
695         * 
696         * @param root - the root directory where the created directory will reside
697         * @param childName - the name of the created directory.
698         * @param message - comment for the creation.
699         * @throws SVNException - if during the creation, there is an error.
700         */
701        private void mkdir( String root,
702                            String childName,
703                            String message ) throws SVNException {
704            SVNNodeKind childKind = repository.checkPath(childName, -1);
705            if (childKind == SVNNodeKind.NONE) {
706                ScmAction addNodeAction = addDirectory(root, childName);
707                SVNActionExecutor executor = new SVNActionExecutor(repository);
708                executor.execute(addNodeAction, message);
709            } else {
710                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Item with name '{0}' can't be created", childName);
711                throw new SVNException(err);
712            }
713        }
714    
715        /**
716         * Create a file.
717         * 
718         * @param path
719         * @param file
720         * @param content
721         * @param message
722         * @throws SVNException
723         */
724        private void newFile( String path,
725                              String file,
726                              byte[] content,
727                              String message ) throws SVNException {
728            SVNNodeKind childKind = repository.checkPath(file, -1);
729            if (childKind == SVNNodeKind.NONE) {
730                ScmAction addFileNodeAction = addFile(path, file, content);
731                SVNActionExecutor executor = new SVNActionExecutor(repository);
732                executor.execute(addFileNodeAction, message);
733            } else {
734                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
735                                                             "Item with name '{0}' can't be created (already exist)",
736                                                             file);
737                throw new SVNException(err);
738            }
739        }
740    
741        /**
742         * {@inheritDoc}
743         * 
744         * @see org.jboss.dna.connector.scm.ScmActionFactory#addDirectory(java.lang.String, java.lang.String)
745         */
746        public ScmAction addDirectory( String root,
747                                       String path ) {
748            return new AddDirectory(root, path);
749        }
750    
751        /**
752         * {@inheritDoc}
753         * 
754         * @see org.jboss.dna.connector.scm.ScmActionFactory#addFile(java.lang.String, java.lang.String, byte[])
755         */
756        public ScmAction addFile( String path,
757                                  String file,
758                                  byte[] content ) {
759            return new AddFile(path, file, content);
760        }
761    
762        /**
763         * {@inheritDoc}
764         * 
765         * @see org.jboss.dna.connector.scm.ScmActionFactory#copyDirectory(java.lang.String, java.lang.String, long)
766         */
767        public ScmAction copyDirectory( String path,
768                                        String newPath,
769                                        long revision ) {
770            return null;
771        }
772    
773        /**
774         * {@inheritDoc}
775         * 
776         * @see org.jboss.dna.connector.scm.ScmActionFactory#deleteDirectory(java.lang.String)
777         */
778        public ScmAction deleteDirectory( String path ) {
779            return null;
780        }
781    
782        /**
783         * {@inheritDoc}
784         * 
785         * @see org.jboss.dna.connector.scm.ScmActionFactory#deleteFile(java.lang.String, java.lang.String)
786         */
787        public ScmAction deleteFile( String path,
788                                     String file ) {
789            return null;
790        }
791    
792        /**
793         * root should be the last, previously created, parent folder. Each directory in the path will be created.
794         */
795        public static class AddDirectory implements ScmAction {
796            private String root;
797            private String path;
798    
799            public AddDirectory( String root,
800                                 String path ) {
801                this.root = root;
802                this.path = path;
803            }
804    
805            public void applyAction( Object context ) throws SVNException {
806    
807                ISVNEditor editor = (ISVNEditor)context;
808    
809                openDirectories(editor, this.root);
810                String[] paths = this.path.split("/");
811                String newPath = this.root;
812                for (int i = 0, length = paths.length; i < length; i++) {
813                    newPath = (newPath.length() != 0) ? newPath + "/" + paths[i] : paths[i];
814    
815                    editor.addDir(newPath, null, -1);
816                }
817    
818                closeDirectories(editor, path);
819                closeDirectories(editor, this.root);
820            }
821        }
822    
823        public static class AddFile implements ScmAction {
824            private String path;
825            private String file;
826            private byte[] content;
827    
828            public AddFile( String path,
829                            String file,
830                            byte[] content ) {
831                this.path = path;
832                this.file = file;
833                this.content = content;
834            }
835    
836            public void applyAction( Object context ) throws Exception {
837                ISVNEditor editor = (ISVNEditor)context;
838                openDirectories(editor, path);
839    
840                editor.addFile(path + "/" + file, null, -1);
841                editor.applyTextDelta(path + "/" + file, null);
842                SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
843                String checksum = deltaGenerator.sendDelta(path + "/" + file, new ByteArrayInputStream(this.content), editor, true);
844                editor.closeFile(path + "/" + file, checksum);
845    
846                closeDirectories(editor, path);
847    
848            }
849    
850        }
851    
852        // private Date getCreatedOn( Object[] objs ) {
853        // Date createdOn = null;
854        // for (Object object : objs) {
855        // if (object instanceof Date) {
856        // createdOn = (Date)object;
857        //
858        // }
859        // }
860        // return createdOn;
861        // }
862    
863        private byte[] getContent( Object[] objs ) {
864            byte[] content = null;
865            for (Object object : objs) {
866                if (object != null && object instanceof Binary) {
867                    Binary buf = (Binary)object;
868                    content = buf.getBytes();
869                }
870            }
871            return content;
872        }
873    
874        private Object[] values( Collection<Property> childNodeProperties ) {
875            Set<Object> result = new HashSet<Object>();
876            for (Property property : childNodeProperties) {
877                result.add(property.getFirstValue());
878            }
879            return result.toArray();
880        }
881    }