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 JazzAddCommand command = new JazzAddCommand(); 254 command.setLogger( getLogger() ); 255 return (AddScmResult) command.execute( repository, fileSet, parameters ); 256 } 257 258 /** 259 * {@inheritDoc} 260 */ 261 protected BranchScmResult branch( ScmProviderRepository repository, ScmFileSet fileSet, 262 CommandParameters parameters ) 263 throws ScmException 264 { 265 JazzBranchCommand command = new JazzBranchCommand(); 266 command.setLogger( getLogger() ); 267 return (BranchScmResult) command.execute( repository, fileSet, parameters ); 268 } 269 270 /** 271 * {@inheritDoc} 272 */ 273 protected BlameScmResult blame( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 274 throws ScmException 275 { 276 JazzBlameCommand command = new JazzBlameCommand(); 277 command.setLogger( getLogger() ); 278 return (BlameScmResult) command.execute( repository, fileSet, parameters ); 279 } 280 281 /** 282 * {@inheritDoc} 283 */ 284 protected ChangeLogScmResult changelog( ScmProviderRepository repository, ScmFileSet fileSet, 285 CommandParameters parameters ) 286 throws ScmException 287 { 288 // We need to call the status command first, so that we can get the details of the workspace. 289 // This is needed for the list changesets command. 290 // We could also 'trust' the value in the pom. 291 JazzStatusCommand statusCommand = new JazzStatusCommand(); 292 statusCommand.setLogger( getLogger() ); 293 statusCommand.execute( repository, fileSet, parameters ); 294 295 JazzChangeLogCommand command = new JazzChangeLogCommand(); 296 command.setLogger( getLogger() ); 297 return (ChangeLogScmResult) command.execute( repository, fileSet, parameters ); 298 } 299 300 /** 301 * {@inheritDoc} 302 */ 303 protected CheckInScmResult checkin( ScmProviderRepository repository, ScmFileSet fileSet, 304 CommandParameters parameters ) 305 throws ScmException 306 { 307 JazzCheckInCommand command = new JazzCheckInCommand(); 308 command.setLogger( getLogger() ); 309 return (CheckInScmResult) command.execute( repository, fileSet, parameters ); 310 } 311 312 /** 313 * {@inheritDoc} 314 */ 315 protected CheckOutScmResult checkout( ScmProviderRepository repository, ScmFileSet fileSet, 316 CommandParameters parameters ) 317 throws ScmException 318 { 319 JazzCheckOutCommand command = new JazzCheckOutCommand(); 320 command.setLogger( getLogger() ); 321 return (CheckOutScmResult) command.execute( repository, fileSet, parameters ); 322 } 323 324 /** 325 * {@inheritDoc} 326 */ 327 protected DiffScmResult diff( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 328 throws ScmException 329 { 330 JazzDiffCommand command = new JazzDiffCommand(); 331 command.setLogger( getLogger() ); 332 return (DiffScmResult) command.execute( repository, fileSet, parameters ); 333 } 334 335 /** 336 * {@inheritDoc} 337 */ 338 protected EditScmResult edit( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 339 throws ScmException 340 { 341 JazzEditCommand command = new JazzEditCommand(); 342 command.setLogger( getLogger() ); 343 return (EditScmResult) command.execute( repository, fileSet, parameters ); 344 } 345 346 /** 347 * {@inheritDoc} 348 */ 349 protected ExportScmResult export( ScmProviderRepository repository, ScmFileSet fileSet, 350 CommandParameters parameters ) 351 throws ScmException 352 { 353 // Use checkout instead 354 return super.export( repository, fileSet, parameters ); 355 } 356 357 /** 358 * {@inheritDoc} 359 */ 360 protected ListScmResult list( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 361 throws ScmException 362 { 363 // We need to call the status command first, so that we can get the details of the stream etc. 364 // This is needed for workspace deliveries and snapshot promotions. 365 JazzStatusCommand statusCommand = new JazzStatusCommand(); 366 statusCommand.setLogger( getLogger() ); 367 statusCommand.execute( repository, fileSet, parameters ); 368 369 JazzListCommand command = new JazzListCommand(); 370 command.setLogger( getLogger() ); 371 return (ListScmResult) command.execute( repository, fileSet, parameters ); 372 } 373 374 /** 375 * {@inheritDoc} 376 */ 377 protected StatusScmResult status( ScmProviderRepository repository, ScmFileSet fileSet, 378 CommandParameters parameters ) 379 throws ScmException 380 { 381 JazzStatusCommand command = new JazzStatusCommand(); 382 command.setLogger( getLogger() ); 383 return (StatusScmResult) command.execute( repository, fileSet, parameters ); 384 } 385 386 /** 387 * {@inheritDoc} 388 */ 389 protected TagScmResult tag( ScmProviderRepository repository, ScmFileSet fileSet, CommandParameters parameters ) 390 throws ScmException 391 { 392 // We need to call the status command first, so that we can get the details of the stream etc. 393 // This is needed for workspace deliveries and snapshot promotions. 394 JazzStatusCommand statusCommand = new JazzStatusCommand(); 395 statusCommand.setLogger( getLogger() ); 396 statusCommand.execute( repository, fileSet, parameters ); 397 398 JazzTagCommand command = new JazzTagCommand(); 399 command.setLogger( getLogger() ); 400 return (TagScmResult) command.execute( repository, fileSet, parameters ); 401 } 402 403 /** 404 * {@inheritDoc} 405 */ 406 protected UpdateScmResult update( ScmProviderRepository repository, ScmFileSet fileSet, 407 CommandParameters parameters ) 408 throws ScmException 409 { 410 JazzUpdateCommand command = new JazzUpdateCommand(); 411 command.setLogger( getLogger() ); 412 return (UpdateScmResult) command.execute( repository, fileSet, parameters ); 413 } 414 415 /** 416 * {@inheritDoc} 417 */ 418 protected UnEditScmResult unedit( ScmProviderRepository repository, ScmFileSet fileSet, 419 CommandParameters parameters ) 420 throws ScmException 421 { 422 JazzUnEditCommand command = new JazzUnEditCommand(); 423 command.setLogger( getLogger() ); 424 return (UnEditScmResult) command.execute( repository, fileSet, parameters ); 425 } 426 427 /** 428 * {@inheritDoc} 429 */ 430 public String getScmSpecificFilename() 431 { 432 return JazzConstants.SCM_META_DATA_FOLDER; 433 } 434 435}