001package org.apache.maven.scm.provider.jazz; 002 003/* 004 * Licensed to the Apache Software Foundation (ASF) under one 005 * or more contributor license agreements. See the NOTICE file 006 * distributed with this work for additional information 007 * regarding copyright ownership. The ASF licenses this file 008 * to you under the Apache License, Version 2.0 (the 009 * "License"); you may not use this file except in compliance 010 * with the License. You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, 015 * software distributed under the License is distributed on an 016 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 017 * KIND, either express or implied. See the License for the 018 * specific language governing permissions and limitations 019 * under the License. 020 */ 021 022import org.apache.maven.scm.CommandParameters; 023import org.apache.maven.scm.ScmException; 024import org.apache.maven.scm.ScmFileSet; 025import org.apache.maven.scm.command.add.AddScmResult; 026import org.apache.maven.scm.command.blame.BlameScmResult; 027import org.apache.maven.scm.command.branch.BranchScmResult; 028import org.apache.maven.scm.command.changelog.ChangeLogScmResult; 029import org.apache.maven.scm.command.checkin.CheckInScmResult; 030import org.apache.maven.scm.command.checkout.CheckOutScmResult; 031import org.apache.maven.scm.command.diff.DiffScmResult; 032import org.apache.maven.scm.command.edit.EditScmResult; 033import org.apache.maven.scm.command.export.ExportScmResult; 034import org.apache.maven.scm.command.list.ListScmResult; 035import org.apache.maven.scm.command.status.StatusScmResult; 036import org.apache.maven.scm.command.tag.TagScmResult; 037import org.apache.maven.scm.command.unedit.UnEditScmResult; 038import org.apache.maven.scm.command.update.UpdateScmResult; 039import org.apache.maven.scm.provider.AbstractScmProvider; 040import org.apache.maven.scm.provider.ScmProviderRepository; 041import org.apache.maven.scm.provider.jazz.command.JazzConstants; 042import org.apache.maven.scm.provider.jazz.command.add.JazzAddCommand; 043import org.apache.maven.scm.provider.jazz.command.blame.JazzBlameCommand; 044import org.apache.maven.scm.provider.jazz.command.branch.JazzBranchCommand; 045import org.apache.maven.scm.provider.jazz.command.changelog.JazzChangeLogCommand; 046import org.apache.maven.scm.provider.jazz.command.checkin.JazzCheckInCommand; 047import org.apache.maven.scm.provider.jazz.command.checkout.JazzCheckOutCommand; 048import org.apache.maven.scm.provider.jazz.command.diff.JazzDiffCommand; 049import org.apache.maven.scm.provider.jazz.command.edit.JazzEditCommand; 050import org.apache.maven.scm.provider.jazz.command.list.JazzListCommand; 051import org.apache.maven.scm.provider.jazz.command.status.JazzStatusCommand; 052import org.apache.maven.scm.provider.jazz.command.tag.JazzTagCommand; 053import org.apache.maven.scm.provider.jazz.command.unedit.JazzUnEditCommand; 054import org.apache.maven.scm.provider.jazz.command.update.JazzUpdateCommand; 055import org.apache.maven.scm.provider.jazz.repository.JazzScmProviderRepository; 056import org.apache.maven.scm.repository.ScmRepositoryException; 057 058import java.net.URI; 059 060/** 061 * The maven scm provider for Jazz. 062 * <p/> 063 * This provider is a wrapper for the command line tool, "scm.sh" or "scm.exe" is that is 064 * part of the Jazz SCM Server. 065 * <p/> 066 * This provider does not use a native API to communicate with the Jazz SCM server. 067 * <p/> 068 * The scm tool itself is documented at: 069 * V2.0.0 - http://publib.boulder.ibm.com/infocenter/rtc/v2r0m0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html 070 * V3.0 - http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html 071 * V3.0.1 - 072 * http://publib.boulder.ibm.com/infocenter/clmhelp/v3r0m1/topic/com.ibm.team.scm.doc/topics/r_scm_cli_scm.html 073 * 074 * @author <a href="mailto:ChrisGWarp@gmail.com">Chris Graham</a> 075 * @plexus.component role="org.apache.maven.scm.provider.ScmProvider" role-hint="jazz" 076 */ 077public class JazzScmProvider 078 extends AbstractScmProvider 079{ 080 // Example: scm:jazz:daviddl;passw0rd123@https://localhost:9443/jazz:Dave's Repository Workspace 081 // If the username or password is supplied, then the @ must be used to delimit them. 082 public static final String JAZZ_URL_FORMAT = 083 "scm:jazz:[username[;password]@]http[s]://server_name[:port]/contextRoot:repositoryWorkspace"; 084 085 // ---------------------------------------------------------------------- 086 // ScmProvider Implementation 087 // ---------------------------------------------------------------------- 088 089 public String getScmType() 090 { 091 return JazzConstants.SCM_TYPE; 092 } 093 094 /** 095 * This method parses the scm URL and returns a SCM provider repository. 096 * At this point, the scmUrl is the part after scm:provider_name: in your SCM URL. 097 * <p/> 098 * The basic url parsing approach is to be as loose as possible. 099 * If you specify as per the docs you'll get what you expect. 100 * If you do something else the result is undefined. 101 * Don't use "/" "\" or "@" as the delimiter. 102 * <p/> 103 * Parse the scmUrl, which will be of the form: 104 * [username[;password]@]http[s]://server_name[:port]/contextRoot:repositoryWorkspace 105 * eg: 106 * Deb;Deb@https://rtc:9444/jazz:BogusRepositoryWorkspace 107 * {@inheritDoc} 108 */ 109 public ScmProviderRepository makeProviderScmRepository( String scmUrl, char delimiter ) 110 throws ScmRepositoryException 111 { 112 // Called from: 113 // AbstractScmProvider.makeScmRepository() 114 // AbstractScmProvider.validateScmUrl() 115 getLogger().debug( "JazzScmProvider:makeProviderScmRepository()" ); 116 getLogger().debug( "Provided scm url - " + scmUrl ); 117 getLogger().debug( "Provided delimiter - '" + delimiter + "'" ); 118 119 String jazzUrlAndWorkspace = null; 120 String usernameAndPassword = null; 121 122 // Look for the Jazz URL after any '@' delimiter used to pass 123 // username/password etc (which may not have been specified) 124 int lastAtPosition = scmUrl.lastIndexOf( '@' ); 125 if ( lastAtPosition == -1 ) 126 { 127 // The username;password@ was not supplied. 128 jazzUrlAndWorkspace = scmUrl; 129 } 130 else 131 { 132 // The username@ or username;password@ was supplied. 133 jazzUrlAndWorkspace = ( lastAtPosition < 0 ) ? scmUrl : scmUrl.substring( lastAtPosition + 1 ); 134 usernameAndPassword = ( lastAtPosition < 0 ) ? null : scmUrl.substring( 0, lastAtPosition ); 135 } 136 137 // jazzUrlAndWorkspace should be: http[s]://server_name:port/contextRoot:repositoryWorkspace 138 // usernameAndPassword should be: username;password or null 139 140 // username and password may not be supplied, and so may remain null. 141 String username = null; 142 String password = null; 143 144 if ( usernameAndPassword != null ) 145 { 146 // Can be: 147 // username 148 // username;password 149 int delimPosition = usernameAndPassword.indexOf( ';' ); 150 username = delimPosition >= 0 ? usernameAndPassword.substring( 0, delimPosition ) : usernameAndPassword; 151 password = delimPosition >= 0 ? usernameAndPassword.substring( delimPosition + 1 ) : null; 152 } 153 154 // We will now validate the jazzUrlAndWorkspace for right number of colons. 155 // This has been observed in the wild, where the contextRoot:repositoryWorkspace was not properly formed 156 // and this resulted in very strange results in the way in which things were parsed. 157 int colonsCounted = 0; 158 int colonIndex = 0; 159 while ( colonIndex != -1 ) 160 { 161 colonIndex = jazzUrlAndWorkspace.indexOf( ":", colonIndex + 1 ); 162 if ( colonIndex != -1 ) 163 { 164 colonsCounted++; 165 } 166 } 167 // havePort may also be true when port is supplied, but otherwise have a malformed URL. 168 boolean havePort = colonsCounted == 3; 169 170 // Look for workspace after the end of the Jazz URL 171 int repositoryWorkspacePosition = jazzUrlAndWorkspace.lastIndexOf( delimiter ); 172 String repositoryWorkspace = jazzUrlAndWorkspace.substring( repositoryWorkspacePosition + 1 ); 173 String jazzUrl = jazzUrlAndWorkspace.substring( 0, repositoryWorkspacePosition ); 174 175 // Validate the protocols. 176 try 177 { 178 // Determine if it is a valid URI. 179 URI jazzUri = URI.create( jazzUrl ); 180 String scheme = jazzUri.getScheme(); 181 getLogger().debug( "Scheme - " + scheme ); 182 if ( scheme == null || !( scheme.equalsIgnoreCase( "http" ) || scheme.equalsIgnoreCase( "https" ) ) ) 183 { 184 throw new ScmRepositoryException( 185 "Jazz Url \"" + jazzUrl + "\" is not a valid URL. The Jazz Url syntax is " + JAZZ_URL_FORMAT ); 186 } 187 } 188 catch ( IllegalArgumentException e ) 189 { 190 throw new ScmRepositoryException( 191 "Jazz Url \"" + jazzUrl + "\" is not a valid URL. The Jazz Url syntax is " + JAZZ_URL_FORMAT ); 192 } 193 194 // At this point, jazzUrl is guaranteed to start with either http:// or https:// 195 // Further process the jazzUrl to extract the server name and port. 196 String hostname = null; 197 int port = 0; 198 199 if ( havePort ) 200 { 201 // jazzUrlAndWorkspace should be: http[s]://server_name:port/contextRoot:repositoryWorkspace 202 // jazzUrl should be : http[s]://server_name:port/contextRoot 203 int protocolIndex = jazzUrl.indexOf( ":" ) + 3; // The +3 accounts for the "://" 204 int portIndex = jazzUrl.indexOf( ":", protocolIndex + 1 ); 205 hostname = jazzUrl.substring( protocolIndex, portIndex ); 206 int pathIndex = jazzUrl.indexOf( "/", portIndex + 1 ); 207 String portNo = jazzUrl.substring( portIndex + 1, pathIndex ); 208 try 209 { 210 port = Integer.parseInt( portNo ); 211 } 212 catch ( NumberFormatException nfe ) 213 { 214 throw new ScmRepositoryException( 215 "Jazz Url \"" + jazzUrl + "\" is not a valid URL. The Jazz Url syntax is " + JAZZ_URL_FORMAT ); 216 } 217 } 218 else 219 { 220 // jazzUrlAndWorkspace should be: http[s]://server_name/contextRoot:repositoryWorkspace 221 // jazzUrl should be : http[s]://server_name/contextRoot 222 // So we will set port to zero. 223 int protocolIndex = jazzUrl.indexOf( ":" ) + 3; // The +3 accounts for the "://" 224 int pathIndex = jazzUrl.indexOf( "/", protocolIndex + 1 ); 225 if ( ( protocolIndex != -1 ) && ( pathIndex != -1 ) ) 226 { 227 hostname = jazzUrl.substring( protocolIndex, pathIndex ); 228 } 229 else 230 { 231 throw new ScmRepositoryException( 232 "Jazz Url \"" + jazzUrl + "\" is not a valid URL. The Jazz Url syntax is " + JAZZ_URL_FORMAT ); 233 } 234 } 235 236 getLogger().debug( "Creating JazzScmProviderRepository with the following values:" ); 237 getLogger().debug( "jazzUrl - " + jazzUrl ); 238 getLogger().debug( "username - " + username ); 239 getLogger().debug( "password - " + password ); 240 getLogger().debug( "hostname - " + hostname ); 241 getLogger().debug( "port - " + port ); 242 getLogger().debug( "repositoryWorkspace - " + repositoryWorkspace ); 243 244 return new JazzScmProviderRepository( jazzUrl, username, password, hostname, port, repositoryWorkspace ); 245 } 246 247 /** 248 * {@inheritDoc} 249 */ 250 public AddScmResult add( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 251 throws ScmException 252 { 253 getLogger().debug( "JazzScmProvider:add()" ); 254 JazzAddCommand command = new JazzAddCommand(); 255 command.setLogger( getLogger() ); 256 return (AddScmResult) command.execute( repository, fileSet, parameters ); 257 } 258 259 /** 260 * {@inheritDoc} 261 */ 262 protected BranchScmResult branch( ScmProviderRepository repository, ScmFileSet fileSet, 263 CommandParameters parameters ) 264 throws ScmException 265 { 266 getLogger().debug( "JazzScmProvider:branch()" ); 267 JazzBranchCommand command = new JazzBranchCommand(); 268 command.setLogger( getLogger() ); 269 return (BranchScmResult) command.execute( repository, fileSet, parameters ); 270 } 271 272 /** 273 * {@inheritDoc} 274 */ 275 protected BlameScmResult blame( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 276 throws ScmException 277 { 278 getLogger().debug( "JazzScmProvider:blame()" ); 279 JazzBlameCommand command = new JazzBlameCommand(); 280 command.setLogger( getLogger() ); 281 return (BlameScmResult) command.execute( repository, fileSet, parameters ); 282 } 283 284 /** 285 * {@inheritDoc} 286 */ 287 protected ChangeLogScmResult changelog( ScmProviderRepository repository, ScmFileSet fileSet, 288 CommandParameters parameters ) 289 throws ScmException 290 { 291 getLogger().debug( "JazzScmProvider:changelog()" ); 292 // We need to call the status command first, so that we can get the details of the workspace. 293 // This is needed for the list changesets command. 294 // We could also 'trust' the value in the pom. 295 JazzStatusCommand statusCommand = new JazzStatusCommand(); 296 statusCommand.setLogger( getLogger() ); 297 statusCommand.execute( repository, fileSet, parameters ); 298 299 JazzChangeLogCommand command = new JazzChangeLogCommand(); 300 command.setLogger( getLogger() ); 301 return (ChangeLogScmResult) command.execute( repository, fileSet, parameters ); 302 } 303 304 /** 305 * {@inheritDoc} 306 */ 307 protected CheckInScmResult checkin( ScmProviderRepository repository, ScmFileSet fileSet, 308 CommandParameters parameters ) 309 throws ScmException 310 { 311 getLogger().debug( "JazzScmProvider:checkin()" ); 312 JazzCheckInCommand command = new JazzCheckInCommand(); 313 command.setLogger( getLogger() ); 314 return (CheckInScmResult) command.execute( repository, fileSet, parameters ); 315 } 316 317 /** 318 * {@inheritDoc} 319 */ 320 protected CheckOutScmResult checkout( ScmProviderRepository repository, ScmFileSet fileSet, 321 CommandParameters parameters ) 322 throws ScmException 323 { 324 getLogger().debug( "JazzScmProvider:checkout()" ); 325 JazzCheckOutCommand command = new JazzCheckOutCommand(); 326 command.setLogger( getLogger() ); 327 return (CheckOutScmResult) command.execute( repository, fileSet, parameters ); 328 } 329 330 /** 331 * {@inheritDoc} 332 */ 333 protected DiffScmResult diff( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 334 throws ScmException 335 { 336 getLogger().debug( "JazzScmProvider:diff()" ); 337 JazzDiffCommand command = new JazzDiffCommand(); 338 command.setLogger( getLogger() ); 339 return (DiffScmResult) command.execute( repository, fileSet, parameters ); 340 } 341 342 /** 343 * {@inheritDoc} 344 */ 345 protected EditScmResult edit( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 346 throws ScmException 347 { 348 getLogger().debug( "JazzScmProvider:edit()" ); 349 JazzEditCommand command = new JazzEditCommand(); 350 command.setLogger( getLogger() ); 351 return (EditScmResult) command.execute( repository, fileSet, parameters ); 352 } 353 354 /** 355 * {@inheritDoc} 356 */ 357 protected ExportScmResult export( ScmProviderRepository repository, ScmFileSet fileSet, 358 CommandParameters parameters ) 359 throws ScmException 360 { 361 getLogger().debug( "JazzScmProvider:export()" ); 362 // Use checkout instead 363 return super.export( repository, fileSet, parameters ); 364 } 365 366 /** 367 * {@inheritDoc} 368 */ 369 protected ListScmResult list( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 370 throws ScmException 371 { 372 getLogger().debug( "JazzScmProvider:list()" ); 373 // We need to call the status command first, so that we can get the details of the stream etc. 374 // This is needed for workspace and component names. 375 JazzStatusCommand statusCommand = new JazzStatusCommand(); 376 statusCommand.setLogger( getLogger() ); 377 statusCommand.execute( repository, fileSet, parameters ); 378 379 JazzListCommand command = new JazzListCommand(); 380 command.setLogger( getLogger() ); 381 return (ListScmResult) command.execute( repository, fileSet, parameters ); 382 } 383 384 /** 385 * {@inheritDoc} 386 */ 387 protected StatusScmResult status( ScmProviderRepository repository, ScmFileSet fileSet, 388 CommandParameters parameters ) 389 throws ScmException 390 { 391 getLogger().debug( "JazzScmProvider:status()" ); 392 JazzStatusCommand command = new JazzStatusCommand(); 393 command.setLogger( getLogger() ); 394 return (StatusScmResult) command.execute( repository, fileSet, parameters ); 395 } 396 397 /** 398 * {@inheritDoc} 399 */ 400 protected TagScmResult tag( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 401 throws ScmException 402 { 403 getLogger().debug( "JazzScmProvider:tag()" ); 404 // We need to call the status command first, so that we can get the details of the stream etc. 405 // This is needed for workspace deliveries and snapshot promotions. 406 JazzStatusCommand statusCommand = new JazzStatusCommand(); 407 statusCommand.setLogger( getLogger() ); 408 statusCommand.execute( repository, fileSet, parameters ); 409 410 JazzTagCommand command = new JazzTagCommand(); 411 command.setLogger( getLogger() ); 412 return (TagScmResult) command.execute( repository, fileSet, parameters ); 413 } 414 415 /** 416 * {@inheritDoc} 417 */ 418 protected UpdateScmResult update( ScmProviderRepository repository, ScmFileSet fileSet, 419 CommandParameters parameters ) 420 throws ScmException 421 { 422 getLogger().debug( "JazzScmProvider:update()" ); 423 JazzUpdateCommand command = new JazzUpdateCommand(); 424 command.setLogger( getLogger() ); 425 return (UpdateScmResult) command.execute( repository, fileSet, parameters ); 426 } 427 428 /** 429 * {@inheritDoc} 430 */ 431 protected UnEditScmResult unedit( ScmProviderRepository repository, ScmFileSet fileSet, 432 CommandParameters parameters ) 433 throws ScmException 434 { 435 getLogger().debug( "JazzScmProvider:unedit()" ); 436 JazzUnEditCommand command = new JazzUnEditCommand(); 437 command.setLogger( getLogger() ); 438 return (UnEditScmResult) command.execute( repository, fileSet, parameters ); 439 } 440 441 /** 442 * {@inheritDoc} 443 */ 444 public String getScmSpecificFilename() 445 { 446 return JazzConstants.SCM_META_DATA_FOLDER; 447 } 448 449}