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.HashSet;
032    import java.util.Set;
033    import org.jboss.dna.common.i18n.I18n;
034    import org.jboss.dna.common.util.Logger;
035    import org.jboss.dna.connector.scm.ScmAction;
036    import org.jboss.dna.connector.scm.ScmActionFactory;
037    import org.jboss.dna.graph.ExecutionContext;
038    import org.jboss.dna.graph.JcrLexicon;
039    import org.jboss.dna.graph.JcrNtLexicon;
040    import org.jboss.dna.graph.Location;
041    import org.jboss.dna.graph.connector.RepositorySourceException;
042    import org.jboss.dna.graph.property.Binary;
043    import org.jboss.dna.graph.property.BinaryFactory;
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.InvalidWorkspaceException;
062    import org.jboss.dna.graph.request.MoveBranchRequest;
063    import org.jboss.dna.graph.request.ReadAllChildrenRequest;
064    import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
065    import org.jboss.dna.graph.request.RenameNodeRequest;
066    import org.jboss.dna.graph.request.Request;
067    import org.jboss.dna.graph.request.UpdatePropertiesRequest;
068    import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
069    import org.jboss.dna.graph.request.processor.RequestProcessor;
070    import org.tmatesoft.svn.core.SVNDirEntry;
071    import org.tmatesoft.svn.core.SVNErrorCode;
072    import org.tmatesoft.svn.core.SVNErrorMessage;
073    import org.tmatesoft.svn.core.SVNException;
074    import org.tmatesoft.svn.core.SVNNodeKind;
075    import org.tmatesoft.svn.core.SVNProperties;
076    import org.tmatesoft.svn.core.SVNProperty;
077    import org.tmatesoft.svn.core.io.ISVNEditor;
078    import org.tmatesoft.svn.core.io.SVNRepository;
079    import org.tmatesoft.svn.core.io.diff.SVNDeltaGenerator;
080    
081    /**
082     * The {@link RequestProcessor} implementation for the file subversion repository connector. This is the class that does the bulk
083     * of the work in the subversion repository connector, since it processes all requests.
084     * 
085     * @author Serge Pagop
086     */
087    public class SVNRepositoryRequestProcessor extends RequestProcessor implements ScmActionFactory {
088    
089        protected static final String BACK_SLASH = "/";
090    
091        private static final String DEFAULT_MIME_TYPE = "application/octet-stream";
092    
093        private final String defaultNamespaceUri;
094        private final boolean updatesAllowed;
095        private SVNRepository defaultWorkspace;
096        protected final Logger logger;
097        private final Set<String> availableWorkspaceNames;
098        private final boolean creatingWorkspacesAllowed;
099        private final RepositoryAccessData accessData;
100    
101        /**
102         * @param sourceName
103         * @param context
104         * @param defaultWorkspace
105         * @param availableWorkspaceNames
106         * @param creatingWorkspacesAllowed
107         * @param updatesAllowed true if this connector supports updating the subversion repository, or false if the connector is read
108         *        only
109         * @param accessData
110         */
111        protected SVNRepositoryRequestProcessor( String sourceName,
112                                                 SVNRepository defaultWorkspace,
113                                                 Set<String> availableWorkspaceNames,
114                                                 boolean creatingWorkspacesAllowed,
115                                                 ExecutionContext context,
116                                                 boolean updatesAllowed,
117                                                 RepositoryAccessData accessData ) {
118            super(sourceName, context, null);
119            assert defaultWorkspace != null;
120            assert availableWorkspaceNames != null;
121            this.defaultNamespaceUri = getExecutionContext().getNamespaceRegistry().getDefaultNamespaceUri();
122            this.updatesAllowed = updatesAllowed;
123            this.defaultWorkspace = defaultWorkspace;
124            this.logger = getExecutionContext().getLogger(getClass());
125            this.availableWorkspaceNames = availableWorkspaceNames;
126            this.creatingWorkspacesAllowed = creatingWorkspacesAllowed;
127            this.accessData = accessData;
128        }
129    
130        /**
131         * {@inheritDoc}
132         * 
133         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadAllChildrenRequest)
134         */
135        @Override
136        public void process( ReadAllChildrenRequest request ) {
137            logger.trace(request.toString());
138    
139            // Get the SVNRepository object that represents the workspace ...
140            SVNRepository workspaceRoot = getWorkspaceDirectory(request.inWorkspace());
141            if (workspaceRoot == null) {
142                request.setError(new InvalidWorkspaceException(
143                                                               SVNRepositoryConnectorI18n.workspaceDoesNotExist.text(request.inWorkspace())));
144                return;
145            }
146            Location myLocation = request.of();
147            Path requestedPath = getPathFor(myLocation, request);
148            // svn connector does not support same name sibling
149            checkThePath(requestedPath, request);
150            // requested path is the root
151            if (requestedPath.isRoot()) {
152                // workspace root must be a directory
153                final Collection<SVNDirEntry> entries = SVNRepositoryUtil.getDir(workspaceRoot, "");
154                for (SVNDirEntry entry : entries) {
155                    // Decide how to represent the children ...
156                    if (entry.getKind() == SVNNodeKind.DIR) {
157                        // Create a Location for each file and directory contained by the parent directory ...
158                        String localName = entry.getName();
159                        Name childName = nameFactory().create(defaultNamespaceUri, localName);
160                        Path childPath = pathFactory().create(requestedPath, childName);
161                        request.addChild(Location.create(childPath));
162                    } else if (entry.getKind() == SVNNodeKind.FILE) {
163                        // The parent is a file, and the path may refer to the node that is either the "nt:file" parent
164                        // node, or the child "jcr:content" node...
165                        String localName = entry.getName();
166                        Path contentPath = pathFactory().create(BACK_SLASH + localName);
167                        if (!contentPath.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
168                            Location location = Location.create(pathFactory().create(contentPath, JcrLexicon.CONTENT));
169                            request.addChild(location);
170                        }
171                    }
172                }
173            } else {
174                try {
175                    SVNNodeKind kind = getNodeKind(workspaceRoot,
176                                                   requestedPath,
177                                                   accessData.getRepositoryRootUrl(),
178                                                   request.inWorkspace());
179                    if (kind == SVNNodeKind.DIR) {
180                        String directoryPath = getPathAsString(requestedPath);
181                        // Decide how to represent the children ...
182                        if (!accessData.getRepositoryRootUrl().equals(request.inWorkspace())) {
183                            directoryPath = directoryPath.substring(1);
184                        }
185                        Collection<SVNDirEntry> dirEntries = SVNRepositoryUtil.getDir(workspaceRoot, directoryPath);
186                        for (SVNDirEntry entry : dirEntries) {
187                            // Decide how to represent the children ...
188                            if (entry.getKind() == SVNNodeKind.DIR) {
189                                // Create a Location for each file and directory contained by the parent directory ...
190                                String localName = entry.getName();
191                                Name childName = nameFactory().create(defaultNamespaceUri, localName);
192                                Path childPath = pathFactory().create(requestedPath, childName);
193                                request.addChild(Location.create(childPath));
194                            } else if (entry.getKind() == SVNNodeKind.FILE) {
195                                // The parent is a file, and the path may refer to the node that is either the "nt:file" parent
196                                // node, or the child "jcr:content" node...
197                                String localName = entry.getName();
198                                Path contentPath = pathFactory().create(getPathAsString(requestedPath) + BACK_SLASH + localName);
199                                Location content = Location.create(pathFactory().create(contentPath, JcrLexicon.CONTENT));
200                                request.addChild(content);
201                            }
202                        }
203                    } else {
204                        if (!requestedPath.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
205                            // Use leading '/' on the requested path
206                            // repository root URL is exactly the same as the workspace
207                            // Get the parent path
208                            String filePath = getPathAsString(requestedPath);
209                            if (!accessData.getRepositoryRootUrl().equals(request.inWorkspace())) {
210                                filePath = filePath.substring(1);
211                            }
212                            Path contentPath = pathFactory().create(requestedPath, JcrLexicon.CONTENT);
213                            Location content = Location.create(contentPath);
214                            request.addChild(content);
215                        }
216                    }
217                } catch (SVNException e) {
218                    request.setError(e);
219                }
220            }
221            request.setActualLocationOfNode(myLocation);
222            setCacheableInfo(request);
223        }
224    
225        /**
226         * {@inheritDoc}
227         * 
228         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadAllPropertiesRequest)
229         */
230        @Override
231        public void process( ReadAllPropertiesRequest request ) {
232            logger.trace(request.toString());
233    
234            // Get the SVNRepository object that represents the workspace ...
235            SVNRepository workspaceRoot = getWorkspaceDirectory(request.inWorkspace());
236            if (workspaceRoot == null) {
237                request.setError(new InvalidWorkspaceException(
238                                                               SVNRepositoryConnectorI18n.workspaceDoesNotExist.text(request.inWorkspace())));
239                return;
240            }
241    
242            // Find the existing file for the parent ...
243            Location myLocation = request.at();
244            Path requestedPath = getPathFor(myLocation, request);
245            if (requestedPath.isRoot()) {
246                // There are no properties on the root ...
247                request.setActualLocationOfNode(myLocation);
248                setCacheableInfo(request);
249                return;
250            }
251    
252            try {
253    
254                SVNNodeKind kind = getNodeKind(workspaceRoot, requestedPath, accessData.getRepositoryRootUrl(), request.inWorkspace());
255                // Generate the properties for this File object ...
256                PropertyFactory factory = getExecutionContext().getPropertyFactory();
257                DateTimeFactory dateFactory = getExecutionContext().getValueFactories().getDateFactory();
258                // Note that we don't have 'created' timestamps, just last modified, so we'll have to use them
259                if (kind == SVNNodeKind.DIR) {
260                    String directoryPath = getPathAsString(requestedPath);
261                    if (!accessData.getRepositoryRootUrl().equals(request.inWorkspace())) {
262                        directoryPath = directoryPath.substring(1);
263                    }
264                    request.addProperty(factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FOLDER));
265                    SVNDirEntry entry = getEntryInfo(workspaceRoot, directoryPath);
266                    request.addProperty(factory.create(JcrLexicon.LAST_MODIFIED, dateFactory.create(entry.getDate())));
267                } else {
268                    if (requestedPath.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
269                        String contentPath = getPathAsString(requestedPath.getParent());
270                        if (!accessData.getRepositoryRootUrl().equals(request.inWorkspace())) {
271                            contentPath = contentPath.substring(1);
272                        }
273                        SVNDirEntry entry = getEntryInfo(workspaceRoot, contentPath);
274                        // The request is to get properties of the "jcr:content" child node ...
275                        request.addProperty(factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.RESOURCE));
276                        request.addProperty(factory.create(JcrLexicon.LAST_MODIFIED, dateFactory.create(entry.getDate())));
277    
278                        ByteArrayOutputStream os = new ByteArrayOutputStream();
279                        SVNProperties fileProperties = new SVNProperties();
280                        getData(contentPath, fileProperties, os);
281                        String mimeType = fileProperties.getStringValue(SVNProperty.MIME_TYPE);
282                        if (mimeType == null) mimeType = DEFAULT_MIME_TYPE;
283                        request.addProperty(factory.create(JcrLexicon.MIMETYPE, mimeType));
284    
285                        if (os.toByteArray().length > 0) {
286                            // Now put the file's content into the "jcr:data" property ...
287                            BinaryFactory binaryFactory = getExecutionContext().getValueFactories().getBinaryFactory();
288                            request.addProperty(factory.create(JcrLexicon.DATA, binaryFactory.create(os.toByteArray())));
289                        }
290    
291                    } else {
292                        String filePath = getPathAsString(requestedPath);
293                        if (!accessData.getRepositoryRootUrl().equals(request.inWorkspace())) {
294                            filePath = filePath.substring(1);
295                        }
296                        // The request is to get properties for the node representing the file
297                        request.addProperty(factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FILE));
298                        ByteArrayOutputStream os = new ByteArrayOutputStream();
299                        SVNProperties fileProperties = new SVNProperties();
300                        getData(filePath, fileProperties, os);
301                        String created = fileProperties.getStringValue(SVNProperty.COMMITTED_DATE);
302                        if (created != null) {
303                            request.addProperty(factory.create(JcrLexicon.CREATED, dateFactory.create(created)));
304                        }
305                    }
306                }
307                request.setActualLocationOfNode(myLocation);
308                setCacheableInfo(request);
309    
310            } catch (SVNException e) {
311                request.setError(e);
312            }
313        }
314    
315        /**
316         * {@inheritDoc}
317         * 
318         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateNodeRequest)
319         */
320        @Override
321        public void process( CreateNodeRequest request ) {
322            updatesAllowed(request);
323        }
324    
325        /**
326         * {@inheritDoc}
327         * 
328         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.UpdatePropertiesRequest)
329         */
330        @Override
331        public void process( UpdatePropertiesRequest request ) {
332            logger.trace(request.toString());
333            verifyUpdatesAllowed();
334        }
335    
336        /**
337         * {@inheritDoc}
338         * 
339         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CopyBranchRequest)
340         */
341        @Override
342        public void process( CopyBranchRequest request ) {
343            updatesAllowed(request);
344        }
345    
346        /**
347         * {@inheritDoc}
348         * 
349         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DeleteBranchRequest)
350         */
351        @Override
352        public void process( DeleteBranchRequest request ) {
353            updatesAllowed(request);
354        }
355    
356        /**
357         * {@inheritDoc}
358         * 
359         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.MoveBranchRequest)
360         */
361        @Override
362        public void process( MoveBranchRequest request ) {
363            updatesAllowed(request);
364        }
365    
366        /**
367         * {@inheritDoc}
368         * 
369         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.RenameNodeRequest)
370         */
371        @Override
372        public void process( RenameNodeRequest request ) {
373            if (updatesAllowed(request)) super.process(request);
374        }
375    
376        /**
377         * {@inheritDoc}
378         * 
379         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.VerifyWorkspaceRequest)
380         */
381        @Override
382        public void process( VerifyWorkspaceRequest request ) {
383            // If the request contains a null name, then we use the default ...
384            String workspaceName = request.workspaceName();
385            if (workspaceName == null) workspaceName = defaultWorkspace.getLocation().toDecodedString();
386    
387            SVNRepository repository = null;
388            if (!this.creatingWorkspacesAllowed) {
389                // Then the workspace name must be one of the available names ...
390                boolean found = false;
391                for (String available : this.availableWorkspaceNames) {
392                    if (workspaceName.equals(available)) {
393                        found = true;
394                        break;
395                    }
396                    repository = SVNRepositoryUtil.createRepository(available, accessData.getUsername(), accessData.getPassword());
397                    // check if the workspace is conform
398                    if (SVNRepositoryUtil.isDirectory(repository, "")
399                        && repository.getLocation().toDecodedString().equals(workspaceName)) {
400                        found = true;
401                        break;
402                    }
403                }
404                if (!found) {
405                    request.setError(new InvalidWorkspaceException(
406                                                                   SVNRepositoryConnectorI18n.workspaceDoesNotExist.text(workspaceName)));
407                    return;
408                }
409            }
410    
411            // Verify that there is a repos at the path given by the workspace name ...
412            repository = SVNRepositoryUtil.createRepository(workspaceName, accessData.getUsername(), accessData.getPassword());
413            if (SVNRepositoryUtil.isDirectory(repository, "")) {
414                request.setActualWorkspaceName(repository.getLocation().toDecodedString());
415                request.setActualRootLocation(Location.create(pathFactory().createRootPath()));
416            } else {
417                request.setError(new InvalidWorkspaceException(SVNRepositoryConnectorI18n.workspaceDoesNotExist.text(workspaceName)));
418            }
419        }
420    
421        /**
422         * {@inheritDoc}
423         * 
424         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.GetWorkspacesRequest)
425         */
426        @Override
427        public void process( GetWorkspacesRequest request ) {
428            // Return the set of available workspace names, even if new workspaces can be created ...
429            Set<String> names = new HashSet<String>();
430            for (String name : this.availableWorkspaceNames) {
431                SVNRepository repos = SVNRepositoryUtil.createRepository(name, accessData.getUsername(), accessData.getPassword());
432                if (repos != null && SVNRepositoryUtil.isDirectory(repos, "")) {
433                    names.add(repos.getLocation().toDecodedString());
434                } else {
435                    request.setError(new InvalidWorkspaceException(SVNRepositoryConnectorI18n.workspaceDoesNotExist.text(name)));
436                }
437            }
438            request.setAvailableWorkspaceNames(Collections.unmodifiableSet(names));
439        }
440    
441        /**
442         * {@inheritDoc}
443         * 
444         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CloneWorkspaceRequest)
445         */
446        @Override
447        public void process( CloneWorkspaceRequest request ) {
448            if (!updatesAllowed) {
449                request.setError(new InvalidRequestException(
450                                                             SVNRepositoryConnectorI18n.sourceDoesNotSupportCloningWorkspaces.text(getSourceName())));
451            }
452        }
453    
454        /**
455         * {@inheritDoc}
456         * 
457         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.CreateWorkspaceRequest)
458         */
459        @Override
460        public void process( CreateWorkspaceRequest request ) {
461            final String workspaceName = request.desiredNameOfNewWorkspace();
462            if (!creatingWorkspacesAllowed) {
463                String msg = SVNRepositoryConnectorI18n.unableToCreateWorkspaces.text(getSourceName(), workspaceName);
464                request.setError(new InvalidRequestException(msg));
465                return;
466            }
467            // This doesn't create the directory representing the workspace (it must already exist), but it will add
468            // the workspace name to the available names ...
469            SVNRepository repository = SVNRepositoryUtil.createRepository(workspaceName,
470                                                                          accessData.getUsername(),
471                                                                          accessData.getPassword());
472            if (SVNRepositoryUtil.isDirectory(repository, "")) {
473                request.setActualWorkspaceName(repository.getLocation().toDecodedString());
474                request.setActualRootLocation(Location.create(pathFactory().createRootPath()));
475                availableWorkspaceNames.add(repository.getLocation().toDecodedString());
476            } else {
477                request.setError(new InvalidWorkspaceException(SVNRepositoryConnectorI18n.workspaceDoesNotExist.text(workspaceName)));
478            }
479    
480        }
481    
482        /**
483         * {@inheritDoc}
484         * 
485         * @see org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.DestroyWorkspaceRequest)
486         */
487        @Override
488        public void process( DestroyWorkspaceRequest request ) {
489            final String workspaceName = request.workspaceName();
490            if (!creatingWorkspacesAllowed) {
491                String msg = SVNRepositoryConnectorI18n.unableToCreateWorkspaces.text(getSourceName(), workspaceName);
492                request.setError(new InvalidRequestException(msg));
493            }
494            // This doesn't delete the file/directory; rather, it just remove the workspace from the available set ...
495            if (!this.availableWorkspaceNames.remove(workspaceName)) {
496                request.setError(new InvalidWorkspaceException(SVNRepositoryConnectorI18n.workspaceDoesNotExist.text(workspaceName)));
497            }
498        }
499    
500        /**
501         * Verify if change is allowed on a specific source.
502         * 
503         * @throws RepositorySourceException if change on that repository source is not allowed.
504         */
505        protected void verifyUpdatesAllowed() {
506            if (!updatesAllowed) {
507                throw new InvalidRequestException(SVNRepositoryConnectorI18n.sourceIsReadOnly.text(getSourceName()));
508            }
509        }
510    
511        protected boolean updatesAllowed( Request request ) {
512            if (!updatesAllowed) {
513                request.setError(new InvalidRequestException(SVNRepositoryConnectorI18n.sourceIsReadOnly.text(getSourceName())));
514            }
515            return !request.hasError();
516        }
517    
518        /**
519         * Factory for sample name.
520         * 
521         * @return the name factory
522         */
523        protected NameFactory nameFactory() {
524            return getExecutionContext().getValueFactories().getNameFactory();
525        }
526    
527        /**
528         * Factory for path creation.
529         * 
530         * @return a path factory.
531         */
532        protected PathFactory pathFactory() {
533            return getExecutionContext().getValueFactories().getPathFactory();
534        }
535    
536        /**
537         * Factory for property creation.
538         * 
539         * @return the property factory.
540         */
541        protected PropertyFactory propertyFactory() {
542            return getExecutionContext().getPropertyFactory();
543        }
544    
545        /**
546         * Factory for date creation.
547         * 
548         * @return the date factory.
549         */
550        protected DateTimeFactory dateFactory() {
551            return getExecutionContext().getValueFactories().getDateFactory();
552        }
553    
554        /**
555         * Factory for binary creation.
556         * 
557         * @return the binary factory..
558         */
559        protected ValueFactory<Binary> binaryFactory() {
560            return getExecutionContext().getValueFactories().getBinaryFactory();
561        }
562    
563        /**
564         * Get the path for a locarion and check if the path is null or not.
565         * 
566         * @param location - the location.
567         * @param request - the requested path.
568         * @return the path.
569         * @throws RepositorySourceException if the path of a location is null.
570         */
571        protected Path getPathFor( Location location,
572                                   Request request ) {
573            Path path = location.getPath();
574            if (path == null) {
575                I18n msg = SVNRepositoryConnectorI18n.locationInRequestMustHavePath;
576                throw new RepositorySourceException(getSourceName(), msg.text(getSourceName(), request));
577            }
578            return path;
579        }
580    
581        /**
582         * Get the content of a file.
583         * 
584         * @param path - the path to that file.
585         * @param properties - the properties of the file.
586         * @param os - the output stream where to store the content.
587         * @throws SVNException - throws if such path is not at that revision or in case of a connection problem.
588         */
589        protected void getData( String path,
590                                SVNProperties properties,
591                                OutputStream os ) throws SVNException {
592            getDefaultWorkspace().getFile(path, -1, properties, os);
593    
594        }
595    
596        /**
597         * Get the repository driver.
598         * 
599         * @return repository
600         */
601        public SVNRepository getDefaultWorkspace() {
602            return defaultWorkspace;
603        }
604    
605        /**
606         * Validate the kind of node and throws an exception if necessary.
607         * 
608         * @param repos
609         * @param requestedPath
610         * @return the kind.
611         */
612        protected SVNNodeKind validateNodeKind( SVNRepository repos,
613                                                Path requestedPath ) {
614            SVNNodeKind kind;
615            String myPath;
616            if (getPathAsString(requestedPath).trim().equals("/")) {
617                myPath = getPathAsString(requestedPath);
618            } else if (requestedPath.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
619                myPath = getPathAsString(requestedPath.getParent());
620            } else {
621                // directory and file
622                myPath = getPathAsString(requestedPath);
623            }
624    
625            try {
626    
627                kind = repos.checkPath(myPath, -1);
628                if (kind == SVNNodeKind.NONE) {
629                    // node does not exist or requested node is not correct.
630                    throw new PathNotFoundException(Location.create(requestedPath), null,
631                                                    SVNRepositoryConnectorI18n.nodeDoesNotExist.text(myPath));
632                } else if (kind == SVNNodeKind.UNKNOWN) {
633                    // node is unknown
634                    throw new PathNotFoundException(Location.create(requestedPath), null,
635                                                    SVNRepositoryConnectorI18n.nodeIsActuallyUnknow.text(myPath));
636                }
637            } catch (SVNException e) {
638                throw new RepositorySourceException(
639                                                    getSourceName(),
640                                                    SVNRepositoryConnectorI18n.connectingFailureOrUserAuthenticationProblem.text(getSourceName()));
641            }
642    
643            return kind;
644        }
645    
646        private String getPathAsString( Path path ) {
647            return path.getString(getExecutionContext().getNamespaceRegistry());
648        }
649    
650        /**
651         * Get some important informations of a path
652         * 
653         * @param repos
654         * @param path - the path
655         * @return - the {@link SVNDirEntry}.
656         */
657        protected SVNDirEntry getEntryInfo( SVNRepository repos,
658                                            String path ) {
659            assert path != null;
660            SVNDirEntry entry = null;
661            try {
662                entry = repos.info(path, -1);
663            } catch (SVNException e) {
664                throw new RepositorySourceException(
665                                                    getSourceName(),
666                                                    SVNRepositoryConnectorI18n.connectingFailureOrUserAuthenticationProblem.text(getSourceName()));
667            }
668            return entry;
669        }
670    
671        /**
672         * Open the directories where change has to be made.
673         * 
674         * @param editor - abstract editor.
675         * @param rootPath - the pa to open.
676         * @throws SVNException when a error occur.
677         */
678        protected static void openDirectories( ISVNEditor editor,
679                                               String rootPath ) throws SVNException {
680            assert rootPath != null;
681            int pos = rootPath.indexOf('/', 0);
682            while (pos != -1) {
683                String dir = rootPath.substring(0, pos);
684                editor.openDir(dir, -1);
685                pos = rootPath.indexOf('/', pos + 1);
686            }
687            String dir = rootPath.substring(0, rootPath.length());
688            editor.openDir(dir, -1);
689        }
690    
691        /**
692         * Close the directories where change was made.
693         * 
694         * @param editor - the abstract editor.
695         * @param path - the directories to open.
696         * @throws SVNException when a error occur.
697         */
698        protected static void closeDirectories( ISVNEditor editor,
699                                                String path ) throws SVNException {
700            int length = path.length() - 1;
701            int pos = path.lastIndexOf('/', length);
702            editor.closeDir();
703            while (pos != -1) {
704                editor.closeDir();
705                pos = path.lastIndexOf('/', pos - 1);
706            }
707        }
708    
709        /**
710         * Get the last revision.
711         * 
712         * @param repos
713         * @return the last revision number.
714         * @throws Exception
715         */
716        public long getLatestRevision( SVNRepository repos ) throws Exception {
717            try {
718                return repos.getLatestRevision();
719            } catch (SVNException e) {
720                e.printStackTrace();
721                // logger.error( "svn error: " );
722                throw e;
723            }
724        }
725    
726        /**
727         * Add directory in a repository
728         * 
729         * @param repository - the repository.
730         * @param root - the root path has to exist.
731         * @param child - new path to be added.
732         * @param message - information about the change action.
733         * @throws SVNException when a error occur.
734         */
735        protected void addDirEntry( SVNRepository repository,
736                                    String root,
737                                    String child,
738                                    String message ) throws SVNException {
739            assert root.trim().length() != 0;
740            SVNNodeKind rootKind = repository.checkPath(root, -1);
741            if (rootKind == SVNNodeKind.UNKNOWN) {
742                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
743                                                             "path with name '{0}' is unknown in the repository",
744                                                             root);
745                throw new SVNException(err);
746            } else if (rootKind == SVNNodeKind.NONE) {
747                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
748                                                             "path with name '{0}' is missing in the repository",
749                                                             root);
750                throw new SVNException(err);
751            } else if (rootKind == SVNNodeKind.FILE) {
752                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
753                                                             "path with name '{0}' is a file, you need a directory",
754                                                             root);
755                throw new SVNException(err);
756            } else if (rootKind == SVNNodeKind.DIR) {
757                ISVNEditor editor = repository.getCommitEditor(message, null, true, null);
758                if (root.length() == 1 && root.charAt(0) == '/') {
759                    addProcess(repository, editor, root, "", child);
760                } else {
761                    String rootPath = root.substring(1);
762                    addProcess(repository, editor, rootPath, null, child);
763                }
764            }
765        }
766    
767        private void addProcess( SVNRepository repos,
768                                 ISVNEditor editor,
769                                 String rootPath,
770                                 String editedRoot,
771                                 String childSegmentName ) throws SVNException {
772            openDirectories(editor, editedRoot);
773            // test if so a directory does not exist.
774            SVNNodeKind childKind = repos.checkPath(childSegmentName, -1);
775            if (childKind == SVNNodeKind.NONE) {
776                editor.addDir(childSegmentName, null, -1);
777                closeDirectories(editor, childSegmentName);
778                if (editedRoot != null) {
779                    closeDirectories(editor, editedRoot);
780                } else {
781                    closeDirectories(editor, rootPath);
782                }
783    
784            } else {
785                closeDirectories(editor, childSegmentName);
786                if (editedRoot != null) {
787                    closeDirectories(editor, editedRoot);
788                } else {
789                    closeDirectories(editor, rootPath);
790                }
791            }
792        }
793    
794        /**
795         * Create a directory .
796         * 
797         * @param repos
798         * @param root - the root directory where the created directory will reside
799         * @param childName - the name of the created directory.
800         * @param message - comment for the creation.
801         * @throws SVNException - if during the creation, there is an error.
802         */
803        @SuppressWarnings( "unused" )
804        private void mkdir( SVNRepository repos,
805                            String root,
806                            String childName,
807                            String message ) throws SVNException {
808            SVNNodeKind childKind = repos.checkPath(childName, -1);
809            if (childKind == SVNNodeKind.NONE) {
810                ScmAction addNodeAction = addDirectory(root, childName);
811                SVNActionExecutor executor = new SVNActionExecutor(repos);
812                executor.execute(addNodeAction, message);
813            } else {
814                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "Item with name '{0}' can't be created", childName);
815                throw new SVNException(err);
816            }
817        }
818    
819        /**
820         * Create a file.
821         * 
822         * @param path
823         * @param file
824         * @param content
825         * @param message
826         * @throws SVNException
827         */
828        @SuppressWarnings( "unused" )
829        private void newFile( String path,
830                              String file,
831                              byte[] content,
832                              String message ) throws SVNException {
833            SVNNodeKind childKind = defaultWorkspace.checkPath(file, -1);
834            if (childKind == SVNNodeKind.NONE) {
835                ScmAction addFileNodeAction = addFile(path, file, content);
836                SVNActionExecutor executor = new SVNActionExecutor(defaultWorkspace);
837                executor.execute(addFileNodeAction, message);
838            } else {
839                SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
840                                                             "Item with name '{0}' can't be created (already exist)",
841                                                             file);
842                throw new SVNException(err);
843            }
844        }
845    
846        /**
847         * {@inheritDoc}
848         * 
849         * @see org.jboss.dna.connector.scm.ScmActionFactory#addDirectory(java.lang.String, java.lang.String)
850         */
851        public ScmAction addDirectory( String root,
852                                       String path ) {
853            return new AddDirectory(root, path);
854        }
855    
856        /**
857         * {@inheritDoc}
858         * 
859         * @see org.jboss.dna.connector.scm.ScmActionFactory#addFile(java.lang.String, java.lang.String, byte[])
860         */
861        public ScmAction addFile( String path,
862                                  String file,
863                                  byte[] content ) {
864            return new AddFile(path, file, content);
865        }
866    
867        /**
868         * {@inheritDoc}
869         * 
870         * @see org.jboss.dna.connector.scm.ScmActionFactory#copyDirectory(java.lang.String, java.lang.String, long)
871         */
872        public ScmAction copyDirectory( String path,
873                                        String newPath,
874                                        long revision ) {
875            return null;
876        }
877    
878        /**
879         * {@inheritDoc}
880         * 
881         * @see org.jboss.dna.connector.scm.ScmActionFactory#deleteDirectory(java.lang.String)
882         */
883        public ScmAction deleteDirectory( String path ) {
884            return null;
885        }
886    
887        /**
888         * {@inheritDoc}
889         * 
890         * @see org.jboss.dna.connector.scm.ScmActionFactory#deleteFile(java.lang.String, java.lang.String)
891         */
892        public ScmAction deleteFile( String path,
893                                     String file ) {
894            return null;
895        }
896    
897        /**
898         * root should be the last, previously created, parent folder. Each directory in the path will be created.
899         */
900        public static class AddDirectory implements ScmAction {
901            private String root;
902            private String path;
903    
904            public AddDirectory( String root,
905                                 String path ) {
906                this.root = root;
907                this.path = path;
908            }
909    
910            public void applyAction( Object context ) throws SVNException {
911    
912                ISVNEditor editor = (ISVNEditor)context;
913    
914                openDirectories(editor, this.root);
915                String[] paths = this.path.split("/");
916                String newPath = this.root;
917                for (int i = 0, length = paths.length; i < length; i++) {
918                    newPath = (newPath.length() != 0) ? newPath + "/" + paths[i] : paths[i];
919    
920                    editor.addDir(newPath, null, -1);
921                }
922    
923                closeDirectories(editor, path);
924                closeDirectories(editor, this.root);
925            }
926        }
927    
928        public static class AddFile implements ScmAction {
929            private String path;
930            private String file;
931            private byte[] content;
932    
933            public AddFile( String path,
934                            String file,
935                            byte[] content ) {
936                this.path = path;
937                this.file = file;
938                this.content = content;
939            }
940    
941            public void applyAction( Object context ) throws Exception {
942                ISVNEditor editor = (ISVNEditor)context;
943                openDirectories(editor, path);
944    
945                editor.addFile(path + "/" + file, null, -1);
946                editor.applyTextDelta(path + "/" + file, null);
947                SVNDeltaGenerator deltaGenerator = new SVNDeltaGenerator();
948                String checksum = deltaGenerator.sendDelta(path + "/" + file, new ByteArrayInputStream(this.content), editor, true);
949                editor.closeFile(path + "/" + file, checksum);
950    
951                closeDirectories(editor, path);
952    
953            }
954    
955        }
956    
957        @SuppressWarnings( "unused" )
958        private byte[] getContent( Object[] objs ) {
959            byte[] content = null;
960            for (Object object : objs) {
961                if (object != null && object instanceof Binary) {
962                    Binary buf = (Binary)object;
963                    content = buf.getBytes();
964                }
965            }
966            return content;
967        }
968    
969        @SuppressWarnings( "unused" )
970        private Object[] values( Collection<Property> childNodeProperties ) {
971            Set<Object> result = new HashSet<Object>();
972            for (Property property : childNodeProperties) {
973                result.add(property.getFirstValue());
974            }
975            return result.toArray();
976        }
977    
978        private void checkThePath( Path path,
979                                   Request request ) {
980            for (Path.Segment segment : path) {
981                // Verify the segment is valid ...
982                if (segment.getIndex() > 1) {
983                    I18n msg = SVNRepositoryConnectorI18n.sameNameSiblingsAreNotAllowed;
984                    throw new RepositorySourceException(getSourceName(), msg.text(getSourceName(), request));
985                }
986                // TODO
987                // if (!segment.getName().getNamespaceUri().equals(defaultNamespaceUri)) {
988                // I18n msg = SVNRepositoryConnectorI18n.onlyTheDefaultNamespaceIsAllowed;
989                // throw new RepositorySourceException(getSourceName(), msg.text(getSourceName(), request));
990                // }
991            }
992        }
993    
994        protected SVNRepository getWorkspaceDirectory( String workspaceName ) {
995            SVNRepository repository = defaultWorkspace;
996            if (workspaceName != null) {
997                SVNRepository repos = SVNRepositoryUtil.createRepository(workspaceName,
998                                                                         accessData.getUsername(),
999                                                                         accessData.getPassword());
1000                if (SVNRepositoryUtil.isDirectory(repos, "")) {
1001                    repository = repos;
1002                } else {
1003                    return null;
1004                }
1005            }
1006            return repository;
1007        }
1008    
1009        protected SVNNodeKind getNodeKind( SVNRepository repository,
1010                                           Path path,
1011                                           String repositoryRootUrl,
1012                                           String inWorkspace ) throws SVNException {
1013            assert path != null;
1014            assert repositoryRootUrl != null;
1015            assert inWorkspace != null;
1016            // See if the path is a "jcr:content" node ...
1017            if (path.getLastSegment().getName().equals(JcrLexicon.CONTENT)) {
1018                // We only want to use the parent path to find the actual file ...
1019                path = path.getParent();
1020            }
1021            String pathAsString = getPathAsString(path);
1022            if (!repositoryRootUrl.equals(inWorkspace)) {
1023                pathAsString = pathAsString.substring(1);
1024            }
1025            SVNNodeKind kind = repository.checkPath(pathAsString, -1);
1026            if (kind == SVNNodeKind.NONE) {
1027                // node does not exist or requested node is not correct.
1028                throw new PathNotFoundException(Location.create(path), null,
1029                                                SVNRepositoryConnectorI18n.nodeDoesNotExist.text(pathAsString));
1030            } else if (kind == SVNNodeKind.UNKNOWN) {
1031                // node is unknown
1032                throw new PathNotFoundException(Location.create(path), null,
1033                                                SVNRepositoryConnectorI18n.nodeIsActuallyUnknow.text(pathAsString));
1034            }
1035            return kind;
1036        }
1037    }