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 }