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