View Javadoc

1   package org.modeshape.connector.svn;
2   
3   import java.io.ByteArrayOutputStream;
4   import java.io.OutputStream;
5   import java.util.ArrayList;
6   import java.util.Arrays;
7   import java.util.Collection;
8   import java.util.Collections;
9   import java.util.HashSet;
10  import java.util.LinkedList;
11  import java.util.List;
12  import java.util.Map;
13  import java.util.Set;
14  import java.util.UUID;
15  import org.modeshape.common.i18n.I18n;
16  import org.modeshape.connector.scm.ScmAction;
17  import org.modeshape.connector.svn.mgnt.AddDirectory;
18  import org.modeshape.connector.svn.mgnt.AddFile;
19  import org.modeshape.connector.svn.mgnt.DeleteEntry;
20  import org.modeshape.connector.svn.mgnt.UpdateFile;
21  import org.modeshape.graph.ExecutionContext;
22  import org.modeshape.graph.JcrLexicon;
23  import org.modeshape.graph.JcrNtLexicon;
24  import org.modeshape.graph.ModeShapeIntLexicon;
25  import org.modeshape.graph.ModeShapeLexicon;
26  import org.modeshape.graph.NodeConflictBehavior;
27  import org.modeshape.graph.connector.RepositorySourceException;
28  import org.modeshape.graph.connector.path.AbstractWritablePathWorkspace;
29  import org.modeshape.graph.connector.path.DefaultPathNode;
30  import org.modeshape.graph.connector.path.PathNode;
31  import org.modeshape.graph.connector.path.WritablePathRepository;
32  import org.modeshape.graph.connector.path.WritablePathWorkspace;
33  import org.modeshape.graph.connector.path.cache.WorkspaceCache;
34  import org.modeshape.graph.property.Binary;
35  import org.modeshape.graph.property.BinaryFactory;
36  import org.modeshape.graph.property.DateTimeFactory;
37  import org.modeshape.graph.property.Name;
38  import org.modeshape.graph.property.NameFactory;
39  import org.modeshape.graph.property.NamespaceRegistry;
40  import org.modeshape.graph.property.Path;
41  import org.modeshape.graph.property.PathFactory;
42  import org.modeshape.graph.property.Property;
43  import org.modeshape.graph.property.PropertyFactory;
44  import org.modeshape.graph.property.Path.Segment;
45  import org.modeshape.graph.request.InvalidRequestException;
46  import org.tmatesoft.svn.core.SVNDirEntry;
47  import org.tmatesoft.svn.core.SVNErrorCode;
48  import org.tmatesoft.svn.core.SVNErrorMessage;
49  import org.tmatesoft.svn.core.SVNException;
50  import org.tmatesoft.svn.core.SVNNodeKind;
51  import org.tmatesoft.svn.core.SVNProperties;
52  import org.tmatesoft.svn.core.SVNProperty;
53  import org.tmatesoft.svn.core.auth.ISVNAuthenticationManager;
54  import org.tmatesoft.svn.core.internal.io.dav.DAVRepositoryFactory;
55  import org.tmatesoft.svn.core.internal.io.fs.FSRepositoryFactory;
56  import org.tmatesoft.svn.core.internal.io.svn.SVNRepositoryFactoryImpl;
57  import org.tmatesoft.svn.core.io.SVNRepository;
58  import org.tmatesoft.svn.core.wc.SVNWCUtil;
59  
60  public class SvnRepository extends WritablePathRepository {
61  
62      private static final String DEFAULT_MIME_TYPE = "application/octet-stream";
63      protected static final byte[] EMPTY_BYTE_ARRAY = new byte[0];
64  
65      protected final SvnRepositorySource source;
66  
67      static {
68          // for DAV (over http and https)
69          DAVRepositoryFactory.setup();
70          // For File
71          FSRepositoryFactory.setup();
72          // for SVN (over svn and svn+ssh)
73          SVNRepositoryFactoryImpl.setup();
74      }
75  
76      public SvnRepository( SvnRepositorySource source ) {
77          super(source);
78  
79          this.source = source;
80          initialize();
81      }
82  
83      @Override
84      protected void initialize() {
85          ExecutionContext context = source.getRepositoryContext().getExecutionContext();
86          for (String workspaceName : source.getPredefinedWorkspaceNames()) {
87              doCreateWorkspace(context, workspaceName);
88          }
89  
90          String defaultWorkspaceName = source.getDefaultWorkspaceName();
91          if (defaultWorkspaceName != null && !workspaces.containsKey(defaultWorkspaceName)) {
92              doCreateWorkspace(context, defaultWorkspaceName);
93          }
94  
95      }
96  
97      public WorkspaceCache getCache( String workspaceName ) {
98          return source.getPathRepositoryCache().getCache(workspaceName);
99      }
100 
101     /**
102      * Internal method that creates a workspace and adds it to the map of active workspaces without checking to see if the source
103      * allows creating workspaces. This is useful when setting up predefined workspaces.
104      * 
105      * @param context the current execution context; may not be null
106      * @param name the name of the workspace to create; may not be null
107      * @return the newly created workspace; never null
108      */
109     private WritablePathWorkspace doCreateWorkspace( ExecutionContext context,
110                                                      String name ) {
111         SvnWorkspace workspace = new SvnWorkspace(name, source.getRootNodeUuid());
112 
113         workspaces.putIfAbsent(name, workspace);
114         return (WritablePathWorkspace)workspaces.get(name);
115 
116     }
117 
118     @Override
119     protected WritablePathWorkspace createWorkspace( ExecutionContext context,
120                                                      String name ) {
121         if (!source.isCreatingWorkspacesAllowed()) {
122             String msg = SvnRepositoryConnectorI18n.unableToCreateWorkspaces.text(getSourceName(), name);
123             throw new InvalidRequestException(msg);
124         }
125 
126         return doCreateWorkspace(context, name);
127     }
128 
129     class SvnWorkspace extends AbstractWritablePathWorkspace {
130 
131         /**
132          * Only certain properties are tolerated when writing content (dna:resource or jcr:resource) nodes. These properties are
133          * implicitly stored (primary type, data) or silently ignored (encoded, mimetype, last modified). The silently ignored
134          * properties must be accepted to stay compatible with the JCR specification.
135          */
136         private final Set<Name> ALLOWABLE_PROPERTIES_FOR_CONTENT = Collections.unmodifiableSet(new HashSet<Name>(
137                                                                                                                  Arrays.asList(new Name[] {
138                                                                                                                      JcrLexicon.PRIMARY_TYPE,
139                                                                                                                      JcrLexicon.DATA,
140                                                                                                                      JcrLexicon.ENCODED,
141                                                                                                                      JcrLexicon.MIMETYPE,
142                                                                                                                      JcrLexicon.LAST_MODIFIED,
143                                                                                                                      JcrLexicon.UUID,
144                                                                                                                      ModeShapeIntLexicon.NODE_DEFINITON})));
145         /**
146          * Only certain properties are tolerated when writing files (nt:file) or folders (nt:folder) nodes. These properties are
147          * implicitly stored in the file or folder (primary type, created).
148          */
149         private final Set<Name> ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER = Collections.unmodifiableSet(new HashSet<Name>(
150                                                                                                                         Arrays.asList(new Name[] {
151                                                                                                                             JcrLexicon.PRIMARY_TYPE,
152                                                                                                                             JcrLexicon.CREATED,
153                                                                                                                             JcrLexicon.UUID,
154                                                                                                                             ModeShapeIntLexicon.NODE_DEFINITON})));
155 
156         private final SVNRepository workspaceRoot;
157 
158         public SvnWorkspace( String name,
159                              UUID rootNodeUuid ) {
160             super(name, rootNodeUuid);
161 
162             workspaceRoot = getWorkspaceDirectory(name);
163 
164             ISVNAuthenticationManager authManager = SVNWCUtil.createDefaultAuthenticationManager(source.getUsername(),
165                                                                                                  source.getPassword());
166             workspaceRoot.setAuthenticationManager(authManager);
167         }
168 
169         public Path getLowestExistingPath( Path path ) {
170             do {
171                 path = path.getParent();
172 
173                 if (getNode(path) != null) {
174                     return path;
175                 }
176             } while (path != null);
177 
178             assert false : "workspace root path was not a valid path";
179             return null;
180         }
181 
182         public PathNode getNode( Path path ) {
183             WorkspaceCache cache = getCache(getName());
184 
185             PathNode node = cache.get(path);
186             if (node != null) return node;
187 
188             ExecutionContext context = source.getRepositoryContext().getExecutionContext();
189             List<Property> properties = new LinkedList<Property>();
190             List<Segment> children = new LinkedList<Segment>();
191 
192             try {
193                 boolean result = readNode(context, this.getName(), path, properties, children);
194                 if (!result) return null;
195             } catch (SVNException ex) {
196                 return null;
197             }
198 
199             UUID uuid = path.isRoot() ? source.getRootNodeUuid() : null;
200             node = new DefaultPathNode(path, uuid, properties, children);
201 
202             cache.set(node);
203             return node;
204         }
205 
206         public PathNode createNode( ExecutionContext context,
207                                     PathNode parentNode,
208                                     Name name,
209                                     Map<Name, Property> properties,
210                                     NodeConflictBehavior conflictBehavior ) {
211 
212             NamespaceRegistry registry = context.getNamespaceRegistry();
213             NameFactory nameFactory = context.getValueFactories().getNameFactory();
214             PathFactory pathFactory = context.getValueFactories().getPathFactory();
215 
216             // New name to commit into the svn repos workspace
217             String newName = name.getString(registry);
218 
219             Property primaryTypeProp = properties.get(JcrLexicon.PRIMARY_TYPE);
220             Name primaryType = primaryTypeProp == null ? null : nameFactory.create(primaryTypeProp.getFirstValue());
221 
222             Path parentPath = parentNode.getPath();
223             String parentPathAsString = parentPath.getString(registry);
224             Path newPath = pathFactory.create(parentPath, name);
225 
226             String newChildPath = null;
227 
228             // File
229             if (JcrNtLexicon.FILE.equals(primaryType)) {
230                 ensureValidProperties(context, properties.values(), ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER);
231                 // Parent node already exist
232                 boolean skipWrite = false;
233 
234                 if (parentPath.isRoot()) {
235                     if (!source.getRepositoryRootUrl().equals(getName())) {
236                         newChildPath = newName;
237                     } else {
238                         newChildPath = "/" + newName;
239                     }
240                 } else {
241                     newChildPath = newPath.getString(registry);
242                     if (!source.getRepositoryRootUrl().equals(getName())) {
243                         newChildPath = newChildPath.substring(1);
244                     }
245                 }
246 
247                 // check if the new name already exist
248                 try {
249                     if (SvnRepositoryUtil.exists(workspaceRoot, newChildPath)) {
250                         if (conflictBehavior.equals(NodeConflictBehavior.APPEND)) {
251                             throw new InvalidRequestException(SvnRepositoryConnectorI18n.sameNameSiblingsAreNotAllowed.text());
252                         } else if (conflictBehavior.equals(NodeConflictBehavior.DO_NOT_REPLACE)) {
253                             skipWrite = true;
254                         }
255                     }
256                 } catch (SVNException e1) {
257                     throw new RepositorySourceException(getSourceName(), e1.getMessage());
258                 }
259 
260                 // Don't try to write if the node conflict behavior is DO_NOT_REPLACE
261                 if (!skipWrite) {
262                     // create a new, empty file
263                     if (newChildPath != null) {
264                         try {
265                             String rootPath = null;
266                             if (parentPath.isRoot()) {
267                                 rootPath = "";
268                             } else {
269                                 rootPath = parentPathAsString;
270                             }
271                             newFile(rootPath, newName, EMPTY_BYTE_ARRAY, null, getName(), workspaceRoot);
272                         } catch (SVNException e) {
273                             I18n msg = SvnRepositoryConnectorI18n.couldNotCreateFile;
274                             throw new RepositorySourceException(getSourceName(), msg.text(parentPathAsString,
275                                                                                           getName(),
276                                                                                           getSourceName(),
277                                                                                           e.getMessage()), e);
278                         }
279                     }
280                 }
281             } else if (JcrNtLexicon.RESOURCE.equals(primaryType) || ModeShapeLexicon.RESOURCE.equals(primaryType)) { // Resource
282                 ensureValidProperties(context, properties.values(), ALLOWABLE_PROPERTIES_FOR_CONTENT);
283                 if (parentPath.isRoot()) {
284                     newChildPath = parentPathAsString;
285                     if (!source.getRepositoryRootUrl().equals(getName())) {
286                         newChildPath = parentPathAsString.substring(1);
287                     }
288                 } else {
289                     newChildPath = parentPathAsString;
290                     if (!source.getRepositoryRootUrl().equals(getName())) {
291                         newChildPath = newChildPath.substring(1);
292                     }
293                 }
294 
295                 if (!JcrLexicon.CONTENT.equals(name)) {
296                     I18n msg = SvnRepositoryConnectorI18n.invalidNameForResource;
297                     throw new RepositorySourceException(getSourceName(), msg.text(parentPathAsString,
298                                                                                   getName(),
299                                                                                   getSourceName(),
300                                                                                   newName));
301                 }
302 
303                 Property parentPrimaryType = parentNode.getProperty(JcrLexicon.PRIMARY_TYPE);
304                 Name parentPrimaryTypeName = parentPrimaryType == null ? null : nameFactory.create(parentPrimaryType.getFirstValue());
305                 if (!JcrNtLexicon.FILE.equals(parentPrimaryTypeName)) {
306                     I18n msg = SvnRepositoryConnectorI18n.invalidPathForResource;
307                     throw new RepositorySourceException(getSourceName(), msg.text(parentPathAsString, getName(), getSourceName()));
308                 }
309 
310                 boolean updateFileContent = true;
311                 switch (conflictBehavior) {
312                     case APPEND:
313                     case REPLACE:
314                     case UPDATE:
315                         // When the "nt:file" parent node was created, it automatically creates the "jcr:content"
316                         // child node with empty content. Therefore, creating a new "jcr:content" node is
317                         // not technically possible (recall that same-name-siblings are not supported in general,
318                         // but certainly not for the "jcr:content" node). Therefore, we can treat all these
319                         // conflict behavior cases as a simple update to the existing "jcr:content" child node.
320                         break;
321                     case DO_NOT_REPLACE:
322                         // TODO check if the file already has content
323                         updateFileContent = false;
324                 }
325 
326                 Property dataProperty = properties.get(JcrLexicon.DATA);
327                 if (dataProperty == null) {
328                     updateFileContent = false; // no content to write, so just continue
329                 }
330                 if (updateFileContent) {
331                     BinaryFactory binaryFactory = context.getValueFactories().getBinaryFactory();
332                     Binary binary = binaryFactory.create(properties.get(JcrLexicon.DATA).getFirstValue());
333                     // get old data
334                     ByteArrayOutputStream contents = new ByteArrayOutputStream();
335                     SVNProperties svnProperties = new SVNProperties();
336                     try {
337                         workspaceRoot.getFile(newChildPath, -1, svnProperties, contents);
338                         byte[] oldData = contents.toByteArray();
339 
340                         // modify the empty old data with the new resource
341                         if (oldData != null) {
342                             String pathToFile;
343                             if (parentPath.isRoot()) {
344                                 pathToFile = "";
345                             } else {
346                                 pathToFile = parentPath.getParent().getString(registry);
347                             }
348                             String fileName = parentPath.getLastSegment().getString(registry);
349 
350                             modifyFile(pathToFile, fileName, oldData, binary.getBytes(), null, getName(), workspaceRoot);
351                         }
352                     } catch (SVNException e) {
353                         I18n msg = SvnRepositoryConnectorI18n.couldNotReadData;
354                         throw new RepositorySourceException(getSourceName(), msg.text(parentPathAsString,
355                                                                                       getName(),
356                                                                                       getSourceName(),
357                                                                                       e.getMessage()), e);
358                     }
359                 }
360 
361             } else if (JcrNtLexicon.FOLDER.equals(primaryType) || primaryType == null) { // Folder
362                 ensureValidProperties(context, properties.values(), ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER);
363                 try {
364                     mkdir(parentPathAsString, newName, null, getName(), workspaceRoot);
365                 } catch (SVNException e) {
366                     I18n msg = SvnRepositoryConnectorI18n.couldNotCreateFile;
367                     throw new RepositorySourceException(getSourceName(), msg.text(parentPathAsString,
368                                                                                   getName(),
369                                                                                   getSourceName(),
370                                                                                   e.getMessage()), e);
371                 }
372             } else {
373                 I18n msg = SvnRepositoryConnectorI18n.unsupportedPrimaryType;
374                 throw new RepositorySourceException(getSourceName(), msg.text(primaryType.getString(registry),
375                                                                               parentPathAsString,
376                                                                               getName(),
377                                                                               getSourceName()));
378             }
379 
380             PathNode node = getNode(newPath);
381 
382             List<Segment> newChildren = new ArrayList<Segment>(parentNode.getChildSegments().size() + 1);
383             newChildren.addAll(parentNode.getChildSegments());
384             newChildren.add(node.getPath().getLastSegment());
385 
386             WorkspaceCache cache = getCache(getName());
387             cache.set(new DefaultPathNode(parentNode.getPath(), parentNode.getUuid(), parentNode.getProperties(), newChildren));
388             cache.set(node);
389 
390             return node;
391         }
392 
393         /**
394          * Create a directory .
395          * 
396          * @param rootDirPath - the root directory where the created directory will reside
397          * @param childDirPath - the name of the created directory.
398          * @param comment - comment for the creation.
399          * @param inWorkspace
400          * @param currentRepository
401          * @throws SVNException - if during the creation, there is an error.
402          */
403         private void mkdir( String rootDirPath,
404                             String childDirPath,
405                             String comment,
406                             String inWorkspace,
407                             SVNRepository currentRepository ) throws SVNException {
408 
409             String tempParentPath = rootDirPath;
410             if (!source.getRepositoryRootUrl().equals(inWorkspace)) {
411                 if (!tempParentPath.equals("/") && tempParentPath.startsWith("/")) {
412                     tempParentPath = tempParentPath.substring(1);
413                 } else if (tempParentPath.equals("/")) {
414                     tempParentPath = "";
415                 }
416             }
417             String checkPath = tempParentPath.length() == 0 ? childDirPath : tempParentPath + "/" + childDirPath;
418             SVNNodeKind nodeKind = null;
419             try {
420                 nodeKind = currentRepository.checkPath(checkPath, -1);
421             } catch (SVNException e) {
422                 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
423                                                              "May be a Connecting problem to the repository or a user's authentication failure: {0}",
424                                                              e.getMessage());
425                 throw new SVNException(err);
426             }
427 
428             if (nodeKind != null && nodeKind == SVNNodeKind.NONE) {
429                 ScmAction addNodeAction = new AddDirectory(rootDirPath, childDirPath);
430                 SvnActionExecutor executor = new SvnActionExecutor(currentRepository);
431                 comment = comment == null ? "Create a new file " + childDirPath : comment;
432                 executor.execute(addNodeAction, comment);
433             } else {
434                 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
435                                                              "Node with name '{0}' can't be created",
436                                                              childDirPath);
437                 throw new SVNException(err);
438             }
439         }
440 
441         /**
442          * Create a file.
443          * 
444          * @param rootDirPath
445          * @param childFilePath
446          * @param content
447          * @param comment
448          * @param inWorkspace
449          * @param currentRepository
450          * @throws SVNException
451          */
452         private void newFile( String rootDirPath,
453                               String childFilePath,
454                               byte[] content,
455                               String comment,
456                               String inWorkspace,
457                               SVNRepository currentRepository ) throws SVNException {
458 
459             String tempParentPath = rootDirPath;
460             if (!source.getRepositoryRootUrl().equals(inWorkspace)) {
461                 if (!tempParentPath.equals("/") && tempParentPath.startsWith("/")) {
462                     tempParentPath = tempParentPath.substring(1);
463                 }
464             }
465             String checkPath = tempParentPath + "/" + childFilePath;
466             SVNNodeKind nodeKind = null;
467             try {
468                 nodeKind = currentRepository.checkPath(checkPath, -1);
469             } catch (SVNException e) {
470                 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
471                                                              "May be a Connecting problem to the repository or a user's authentication failure: {0}",
472                                                              e.getMessage());
473                 throw new SVNException(err);
474             }
475 
476             if (nodeKind != null && nodeKind == SVNNodeKind.NONE) {
477                 ScmAction addFileNodeAction = new AddFile(rootDirPath, childFilePath, content);
478                 SvnActionExecutor executor = new SvnActionExecutor(currentRepository);
479                 comment = comment == null ? "Create a new file " + childFilePath : comment;
480                 executor.execute(addFileNodeAction, comment);
481             } else {
482                 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
483                                                              "Item with name '{0}' can't be created (already exist)",
484                                                              childFilePath);
485                 throw new SVNException(err);
486             }
487         }
488 
489         /**
490          * Modify a file
491          * 
492          * @param rootPath
493          * @param fileName
494          * @param oldData
495          * @param newData
496          * @param comment
497          * @param inWorkspace
498          * @param currentRepository
499          * @throws SVNException
500          */
501         private void modifyFile( String rootPath,
502                                  String fileName,
503                                  byte[] oldData,
504                                  byte[] newData,
505                                  String comment,
506                                  String inWorkspace,
507                                  SVNRepository currentRepository ) throws SVNException {
508             assert rootPath != null;
509             assert fileName != null;
510             assert oldData != null;
511             assert inWorkspace != null;
512             assert currentRepository != null;
513 
514             try {
515 
516                 if (!source.getRepositoryRootUrl().equals(inWorkspace)) {
517                     if (rootPath.equals("/")) {
518                         rootPath = "";
519                     } else {
520                         rootPath = rootPath.substring(1) + "/";
521                     }
522                 } else {
523                     if (!rootPath.equals("/")) {
524                         rootPath = rootPath + "/";
525                     }
526                 }
527                 String path = rootPath + fileName;
528 
529                 SVNNodeKind nodeKind = currentRepository.checkPath(path, -1);
530                 if (nodeKind == SVNNodeKind.NONE || nodeKind == SVNNodeKind.UNKNOWN) {
531                     SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.ENTRY_NOT_FOUND,
532                                                                  "Item with name '{0}' can't be found",
533                                                                  path);
534                     throw new SVNException(err);
535                 }
536 
537                 ScmAction modifyFileAction = new UpdateFile(rootPath, fileName, oldData, newData);
538                 SvnActionExecutor executor = new SvnActionExecutor(currentRepository);
539                 comment = comment == null ? "modify the " + fileName : comment;
540                 executor.execute(modifyFileAction, comment);
541 
542             } catch (SVNException e) {
543                 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN, "This error is appeared: " + e.getMessage());
544                 throw new SVNException(err, e);
545             }
546 
547         }
548 
549         /**
550          * Delete entry from the repository
551          * 
552          * @param path
553          * @param comment
554          * @param inWorkspace
555          * @param currentRepository
556          * @throws SVNException
557          */
558         private void eraseEntry( String path,
559                                  String comment,
560                                  String inWorkspace,
561                                  SVNRepository currentRepository ) throws SVNException {
562             assert path != null;
563             assert inWorkspace != null;
564             if (path.equals("/") || path.equals("")) {
565                 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.BAD_URL, "The root directory cannot be deleted");
566                 throw new SVNException(err);
567             }
568 
569             try {
570                 ScmAction deleteEntryAction = new DeleteEntry(path);
571                 SvnActionExecutor executor = new SvnActionExecutor(currentRepository);
572                 comment = comment == null ? "Delete the " + path : comment;
573                 executor.execute(deleteEntryAction, comment);
574             } catch (SVNException e) {
575                 SVNErrorMessage err = SVNErrorMessage.create(SVNErrorCode.UNKNOWN,
576                                                              "unknow error during delete action: {0)",
577                                                              e.getMessage());
578                 throw new SVNException(err);
579             }
580         }
581 
582         public boolean removeNode( ExecutionContext context,
583                                    Path nodePath ) {
584 
585             NamespaceRegistry registry = context.getNamespaceRegistry();
586 
587             boolean isContentNode = !nodePath.isRoot() && JcrLexicon.CONTENT.equals(nodePath.getLastSegment().getName());
588             Path actualPath = isContentNode ? nodePath.getParent() : nodePath;
589 
590             try {
591                 SVNNodeKind kind = getNodeKind(context, actualPath, source.getRepositoryRootUrl());
592 
593                 if (kind == SVNNodeKind.NONE) {
594                     return false;
595                 }
596 
597                 if (isContentNode) {
598                     String rootPath = actualPath.getParent().getString(registry);
599                     String fileName = actualPath.getLastSegment().getString(registry);
600                     modifyFile(rootPath, fileName, EMPTY_BYTE_ARRAY, EMPTY_BYTE_ARRAY, null, getName(), workspaceRoot);
601                 } else {
602                     eraseEntry(actualPath.getString(registry), null, getName(), workspaceRoot);
603                 }
604             } catch (SVNException e) {
605                 throw new RepositorySourceException(getSourceName(),
606                                                     SvnRepositoryConnectorI18n.deleteFailed.text(nodePath, getSourceName()));
607             }
608 
609             getCache(getName()).invalidate(nodePath);
610 
611             return true;
612         }
613 
614         public PathNode setProperties( ExecutionContext context,
615                                        Path nodePath,
616                                        Map<Name, Property> properties ) {
617             PathNode targetNode = getNode(nodePath);
618             if (targetNode == null) return null;
619 
620             /*
621              * You can't really remove any properties from SVN nodes.
622              * You can clear the data of a dna:resource though
623              */
624 
625             NameFactory nameFactory = context.getValueFactories().getNameFactory();
626             Property primaryTypeProperty = targetNode.getProperty(JcrLexicon.PRIMARY_TYPE);
627             Name primaryTypeName = primaryTypeProperty == null ? null : nameFactory.create(primaryTypeProperty.getFirstValue());
628             if (ModeShapeLexicon.RESOURCE.equals(primaryTypeName)) {
629 
630                 for (Map.Entry<Name, Property> entry : properties.entrySet()) {
631                     if (JcrLexicon.DATA.equals(entry.getKey())) {
632                         NamespaceRegistry registry = context.getNamespaceRegistry();
633                         byte[] data;
634                         if (entry.getValue() == null) {
635                             data = EMPTY_BYTE_ARRAY;
636                         } else {
637                             BinaryFactory binaryFactory = context.getValueFactories().getBinaryFactory();
638                             data = binaryFactory.create(entry.getValue().getFirstValue()).getBytes();
639 
640                         }
641 
642                         try {
643                             Path actualPath = nodePath.getParent();
644                             modifyFile(actualPath.getParent().getString(registry),
645                                        actualPath.getLastSegment().getString(registry),
646                                        EMPTY_BYTE_ARRAY,
647                                        data,
648                                        "",
649                                        getName(),
650                                        workspaceRoot);
651 
652                             PathNode node = getNode(nodePath);
653                             getCache(getName()).set(node);
654 
655                             return node;
656                         } catch (SVNException ex) {
657                             throw new RepositorySourceException(getSourceName(),
658                                                                 SvnRepositoryConnectorI18n.deleteFailed.text(nodePath,
659                                                                                                              getSourceName()), ex);
660                         }
661                     }
662                 }
663             }
664 
665             return targetNode;
666         }
667 
668         protected boolean readNode( ExecutionContext context,
669                                     String workspaceName,
670                                     Path requestedPath,
671                                     List<Property> properties,
672                                     List<Segment> children ) throws SVNException {
673             PathFactory pathFactory = context.getValueFactories().getPathFactory();
674             NamespaceRegistry registry = context.getNamespaceRegistry();
675 
676             if (requestedPath.isRoot()) {
677                 // workspace root must be a directory
678                 if (children != null) {
679                     final Collection<SVNDirEntry> entries = SvnRepositoryUtil.getDir(workspaceRoot, "");
680                     for (SVNDirEntry entry : entries) {
681                         // All of the children of a directory will be another directory or a file, but never a "jcr:content" node
682                         // ...
683                         children.add(pathFactory.createSegment(entry.getName()));
684                     }
685                 }
686                 // There are no properties on the root ...
687             } else {
688                 // Generate the properties for this File object ...
689                 PropertyFactory factory = context.getPropertyFactory();
690                 DateTimeFactory dateFactory = context.getValueFactories().getDateFactory();
691 
692                 // Figure out the kind of node this represents ...
693                 SVNNodeKind kind = getNodeKind(context, requestedPath, source.getRepositoryRootUrl());
694                 if (kind == SVNNodeKind.NONE) {
695                     // The node doesn't exist
696                     return false;
697                 }
698                 if (kind == SVNNodeKind.DIR) {
699                     String directoryPath = requestedPath.getString(registry);
700                     if (!source.getRepositoryRootUrl().equals(workspaceName)) {
701                         directoryPath = directoryPath.substring(1);
702                     }
703                     if (children != null) {
704                         // Decide how to represent the children ...
705                         Collection<SVNDirEntry> dirEntries = SvnRepositoryUtil.getDir(workspaceRoot, directoryPath);
706                         for (SVNDirEntry entry : dirEntries) {
707                             // All of the children of a directory will be another directory or a file,
708                             // but never a "jcr:content" node ...
709                             children.add(pathFactory.createSegment(entry.getName()));
710                         }
711                     }
712                     if (properties != null) {
713                         // Load the properties for this directory ......
714                         properties.add(factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FOLDER));
715                         SVNDirEntry entry = getEntryInfo(workspaceRoot, directoryPath);
716                         if (entry != null) {
717                             properties.add(factory.create(JcrLexicon.CREATED, dateFactory.create(entry.getDate())));
718                         }
719                     }
720                 } else {
721                     // It's not a directory, so must be a file; the only child of an nt:file is the "jcr:content" node
722                     // ...
723                     if (requestedPath.endsWith(JcrLexicon.CONTENT)) {
724                         // There are never any children of these nodes, just properties ...
725                         if (properties != null) {
726                             String contentPath = requestedPath.getParent().getString(registry);
727                             if (!source.getRepositoryRootUrl().equals(workspaceName)) {
728                                 contentPath = contentPath.substring(1);
729                             }
730                             SVNDirEntry entry = getEntryInfo(workspaceRoot, contentPath);
731                             if (entry != null) {
732                                 // The request is to get properties of the "jcr:content" child node ...
733                                 // Do NOT use "nt:resource", since it extends "mix:referenceable". The JCR spec
734                                 // does not require that "jcr:content" is of type "nt:resource", but rather just
735                                 // suggests it. Therefore, we can use "dna:resource", which is identical to
736                                 // "nt:resource" except it does not extend "mix:referenceable"
737                                 properties.add(factory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.RESOURCE));
738                                 properties.add(factory.create(JcrLexicon.LAST_MODIFIED, dateFactory.create(entry.getDate())));
739                             }
740 
741                             ByteArrayOutputStream os = new ByteArrayOutputStream();
742                             SVNProperties fileProperties = new SVNProperties();
743                             getData(contentPath, fileProperties, os);
744                             String mimeType = fileProperties.getStringValue(SVNProperty.MIME_TYPE);
745                             if (mimeType == null) mimeType = DEFAULT_MIME_TYPE;
746                             properties.add(factory.create(JcrLexicon.MIMETYPE, mimeType));
747 
748                             if (os.toByteArray().length > 0) {
749                                 // Now put the file's content into the "jcr:data" property ...
750                                 BinaryFactory binaryFactory = context.getValueFactories().getBinaryFactory();
751                                 properties.add(factory.create(JcrLexicon.DATA, binaryFactory.create(os.toByteArray())));
752                             }
753                         }
754                     } else {
755                         // Determine the corresponding file path for this object ...
756                         String filePath = requestedPath.getString(registry);
757                         if (!source.getRepositoryRootUrl().equals(workspaceName)) {
758                             filePath = filePath.substring(1);
759                         }
760                         if (children != null) {
761                             // Not a "jcr:content" child node but rather an nt:file node, so add the child ...
762                             children.add(pathFactory.createSegment(JcrLexicon.CONTENT));
763                         }
764                         if (properties != null) {
765                             // Now add the properties to "nt:file" ...
766                             properties.add(factory.create(JcrLexicon.PRIMARY_TYPE, JcrNtLexicon.FILE));
767                             ByteArrayOutputStream os = new ByteArrayOutputStream();
768                             SVNProperties fileProperties = new SVNProperties();
769                             getData(filePath, fileProperties, os);
770                             String created = fileProperties.getStringValue(SVNProperty.COMMITTED_DATE);
771                             properties.add(factory.create(JcrLexicon.CREATED, dateFactory.create(created)));
772                         }
773                     }
774                 }
775             }
776             return true;
777         }
778 
779         /**
780          * Get some important informations of a path
781          * 
782          * @param repos
783          * @param path - the path
784          * @return - the {@link SVNDirEntry}, or null if there is no such entry
785          */
786         protected SVNDirEntry getEntryInfo( SVNRepository repos,
787                                             String path ) {
788             assert path != null;
789             SVNDirEntry entry = null;
790             try {
791                 entry = repos.info(path, -1);
792             } catch (SVNException e) {
793                 throw new RepositorySourceException(
794                                                     getSourceName(),
795                                                     SvnRepositoryConnectorI18n.connectingFailureOrUserAuthenticationProblem.text(getSourceName()));
796             }
797             return entry;
798         }
799 
800         /**
801          * Get the content of a file.
802          * 
803          * @param path - the path to that file.
804          * @param properties - the properties of the file.
805          * @param os - the output stream where to store the content.
806          * @throws SVNException - throws if such path is not at that revision or in case of a connection problem.
807          */
808         protected void getData( String path,
809                                 SVNProperties properties,
810                                 OutputStream os ) throws SVNException {
811             workspaceRoot.getFile(path, -1, properties, os);
812 
813         }
814 
815         protected SVNNodeKind getNodeKind( ExecutionContext context,
816                                            Path path,
817                                            String repositoryRootUrl ) throws SVNException {
818             assert path != null;
819             assert repositoryRootUrl != null;
820 
821             // See if the path is a "jcr:content" node ...
822             if (path.endsWith(JcrLexicon.CONTENT)) {
823                 // We only want to use the parent path to find the actual file ...
824                 path = path.getParent();
825             }
826             String pathAsString = path.getString(context.getNamespaceRegistry());
827             if (!repositoryRootUrl.equals(getName())) {
828                 pathAsString = pathAsString.substring(1);
829             }
830 
831             String absolutePath = pathAsString;
832             SVNNodeKind kind = workspaceRoot.checkPath(absolutePath, -1);
833             if (kind == SVNNodeKind.UNKNOWN) {
834                 // node is unknown
835                 throw new RepositorySourceException(getSourceName(),
836                                                     SvnRepositoryConnectorI18n.nodeIsActuallyUnknow.text(pathAsString));
837             }
838             return kind;
839         }
840 
841         protected SVNRepository getWorkspaceDirectory( String workspaceName ) {
842             if (workspaceName == null) workspaceName = source.getDefaultWorkspaceName();
843 
844             workspaceName = source.getRepositoryRootUrl() + workspaceName;
845 
846             SVNRepository repository = null;
847             SVNRepository repos = SvnRepositoryUtil.createRepository(workspaceName, source.getUsername(), source.getPassword());
848             if (SvnRepositoryUtil.isDirectory(repos, "")) {
849                 repository = repos;
850             } else {
851                 return null;
852             }
853             return repository;
854         }
855 
856         /**
857          * Checks that the collection of {@code properties} only contains properties with allowable names.
858          * 
859          * @param context
860          * @param properties
861          * @param validPropertyNames
862          * @throws RepositorySourceException if {@code properties} contains a
863          * @see #ALLOWABLE_PROPERTIES_FOR_CONTENT
864          * @see #ALLOWABLE_PROPERTIES_FOR_FILE_OR_FOLDER
865          */
866         protected void ensureValidProperties( ExecutionContext context,
867                                               Collection<Property> properties,
868                                               Set<Name> validPropertyNames ) {
869             List<String> invalidNames = new LinkedList<String>();
870             NamespaceRegistry registry = context.getNamespaceRegistry();
871 
872             for (Property property : properties) {
873                 if (!validPropertyNames.contains(property.getName())) {
874                     invalidNames.add(property.getName().getString(registry));
875                 }
876             }
877 
878             if (!invalidNames.isEmpty()) {
879                 throw new RepositorySourceException(getSourceName(),
880                                                     SvnRepositoryConnectorI18n.invalidPropertyNames.text(invalidNames.toString()));
881             }
882         }
883 
884     }
885 
886 }