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 }