package com.perforce.maven.scm.provider.p4.manager; import java.io.File; import java.io.IOException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.io.FileUtils; import org.apache.maven.scm.ScmException; import org.codehaus.plexus.logging.AbstractLogEnabled; import org.codehaus.plexus.personality.plexus.lifecycle.phase.Disposable; import org.codehaus.plexus.util.StringUtils; import com.perforce.maven.scm.provider.p4.repository.P4ScmProviderRepository; import com.perforce.maven.scm.provider.p4.util.P4Utils; import com.perforce.p4java.client.IClient; import com.perforce.p4java.client.IClientSummary; import com.perforce.p4java.client.IClientViewMapping; import com.perforce.p4java.core.file.FileSpecBuilder; import com.perforce.p4java.core.file.FileSpecOpStatus; import com.perforce.p4java.core.file.IFileSpec; import com.perforce.p4java.exception.P4JavaException; import com.perforce.p4java.impl.generic.client.ClientView; import com.perforce.p4java.impl.generic.client.ClientView.ClientViewMapping; import com.perforce.p4java.impl.mapbased.client.Client; import com.perforce.p4java.server.IServer; /** * Manage IClient cache * * @plexus.component role="com.perforce.maven.scm.provider.p4.manager.P4ClientManager" */ public class DefaultP4ClientManager extends AbstractLogEnabled implements P4ClientManager, Disposable { /** * @plexus.requirement role="com.perforce.maven.scm.provider.p4.manager.P4ServerManager" */ private P4ServerManager serverManager; private Map<String, InternalClient> clients = new ConcurrentHashMap<String, InternalClient>(); public void dispose() { for ( InternalClient internalClient : clients.values() ) { if ( internalClient.getRemoveWhenDone() ) { IClient client = internalClient.getClient(); try { client.getServer().deleteClient( client.getName(), false ); client.getServer().disconnect(); } catch ( P4JavaException e ) { this.getLogger().error( e.getLocalizedMessage() ); } } } clients.clear(); } public IClient getClient( P4ScmProviderRepository repo, File rootDir ) throws ScmException { return getClient( repo, rootDir, null ); } public IClient getClient( P4ScmProviderRepository repo, File rootDir, String clientName ) throws ScmException { if ( StringUtils.isBlank( clientName ) ) { clientName = this.discoverClientName( repo, rootDir ); if ( clientName == null ) { clientName = generateClientName( repo, rootDir ); } } IClient client = null; try { InternalClient internalClient = clients.get( getClientKey( repo, clientName, rootDir ) ); if ( internalClient != null ) { client = internalClient.getClient(); if ( !isValidClient( client ) ) { this.getLogger().warn( "Staled cached client, creating new one ..." ); clients.remove( getClientKey( repo, clientName, rootDir ) ); client = null; } } if ( client == null ) { boolean removeWhenDone = false; IServer server = serverManager.createServer( repo ); client = server.getClient( clientName ); if ( client == null ) { client = createNewClient( server, repo, clientName, rootDir ); } if ( client.getName().equals( generateClientName( repo, rootDir ) ) ) { // this may be also what left off from previous process removeWhenDone = true; } this.adjustClientSpecDueToP4JAVAIssue( client, repo ); // no need to set working directory since it is very dynamic // each command must use absolute path instead // server.setWorkingDirectory( rootDir.getAbsolutePath() ); this.clients.put( getClientKey( repo, clientName, rootDir ), new InternalClient( client, removeWhenDone ) ); } client.getServer().setCurrentClient( client ); // so that we can sync } catch ( P4JavaException e ) { throw new ScmException( e.getLocalizedMessage(), e ); } catch ( UnknownHostException e ) { throw new ScmException( e.getLocalizedMessage(), e ); } return client; } private boolean isValidClient( IClient client ) { return DefaultP4ServerManager.isValidServer( client.getServer() ); } /** * adjust client view by additionally insert the intended entry from pom/connectionUrl if needed since the provide * client may not have it. There is also a bug which server incorrectly setup the client view after it is first * created, So we need to check and append a actual view to override the first one. * * @param client * @param repo * @throws P4JavaException */ private void adjustClientSpecDueToP4JAVAIssue( IClient client, P4ScmProviderRepository repo ) throws P4JavaException { ClientView clientView = client.getClientView(); if ( clientView == null ) { clientView = new ClientView(); } ClientViewMapping clientViewMapping = createClientViewMapping( repo, client.getName() ); List<IClientViewMapping> list = clientView.getEntryList(); boolean exists = false; if ( list != null ) { for ( IClientViewMapping map : list ) { if ( map.getDepotSpec().equals( clientViewMapping.getDepotSpec() ) && map.getClient().equals( clientViewMapping.getClient() ) ) { exists = true; break; } } } if ( !exists ) { clientView.addEntry( clientViewMapping ); client.setClientView( clientView ); if ( client.canUpdate() ) { client.update(); } if ( client.canRefresh() ) { client.refresh(); } } } private IClient createNewClient( IServer server, P4ScmProviderRepository repo, String clientName, File basedir ) throws P4JavaException, UnknownHostException { // Create a new client ClientViewMapping clientViewMapping = createClientViewMapping( repo, clientName ); ClientView clientView = new ClientView(); clientView.addEntry( clientViewMapping ); IClient newClient = new Client( clientName, null, null, "Created by P4Maven", InetAddress.getLocalHost().getHostName(), repo.getUser(), basedir.getAbsolutePath(), null, null, null, null, server, clientView ); server.createClient( newClient ); return server.getClient( clientName ); // there is a bug, where the return IClient does not have the same requested ClientViewMapping // for example // //guest/dantran/p4maven/... //someclient/... // become // //guest/dantran/... //someclient/... } private String getClientKey( P4ScmProviderRepository repo, String clientName, File rootDir ) { return repo.getP4Port() + ":" + clientName + ":" + rootDir.getAbsolutePath(); } /** * Generate a Perforce client workspace name. * * @return the string */ private String generateClientName( P4ScmProviderRepository repo, File basedir ) { String username = repo.getUser(); String hostname = repo.getHost(); String path = null; try { hostname = InetAddress.getLocalHost().getHostName(); path = encodeWildcards( basedir.getCanonicalPath().replaceAll( "[/ ~]", "-" ).replaceAll( ",", "" ) ); } catch ( UnknownHostException e ) { this.getLogger().error( e.getMessage(), e ); } catch ( IOException e ) { this.getLogger().error( e.getMessage(), e ); } return username + "-" + hostname + "-MavenSCM-" + path + "-" + StringUtils.replace( repo.getPath(), '/', '\\' ); } /** * Perforce wildcards expansion. * * @param filePath the file path * @return the string */ public static String encodeWildcards( String filePath ) { String path = new String(); if ( filePath != null ) { path = filePath.replaceAll( "%", "%25" ).replaceAll( "\\*", "%2A" ).replaceAll( "#", "%23" ).replaceAll( "@", "%40" ); } return path; } protected ClientViewMapping createClientViewMapping( P4ScmProviderRepository repo, String clientName ) { // Create a new client String viewPath = P4Utils.getCanonicalRepoPath( repo.getPath() ); return new ClientViewMapping( 0, viewPath, "//" + clientName + "/..." ); } private class InternalClient { private final IClient client; public IClient getClient() { return client; } public boolean getRemoveWhenDone() { return removeWhenDone; } private final boolean removeWhenDone; public InternalClient( IClient client, boolean removeWhenDone ) { this.client = client; this.removeWhenDone = new Boolean( removeWhenDone ); } } /** * From a given base directory ( ie a maven project with a pom.xml), look up and existing client that hosts this * project which may also be a sub project of the client's root directory. * * @param repo * @param baseDir * @return * @throws ScmException */ public String discoverClientName( P4ScmProviderRepository repo, File baseDir ) throws ScmException { IServer server = this.serverManager.getServer( repo ); try { List<IClientSummary> clientInfos = server.getClients( repo.getUser(), null, Integer.MAX_VALUE ); for ( IClientSummary clientInfo : clientInfos ) { // make sure to doc this case, where the host field must match with current host if ( !InetAddress.getLocalHost().getHostName().equals( clientInfo.getHostName() ) ) { continue; } IClient client = server.getClient( clientInfo ); if ( baseDir.equals( new File( clientInfo.getRoot() ) ) ) { // one dir may be used by 2 separate clients/depo paths, like maven-scm-tck branch test if ( clientHasTheMatchingDepotPath( client, repo ) ) { return clientInfo.getName(); } } else { // see if repo's basedir is a subdirectory of an existing client File clientRootDir = new File( clientInfo.getRoot() ); if ( clientRootDir.isDirectory() && FileUtils.directoryContains( clientRootDir, baseDir ) ) { // see if the client really has this directory. // IClient client = server.getClient( clientInfo ); // this.adjustClientSpecDueToP4JAVAIssue( client, repo ); server.setCurrentClient( client ); // assume this operation need a pom like release plugin File currentPom = new File( baseDir, "pom.xml" ); if ( currentPom.exists() ) { List<IFileSpec> remoteFiles = server.getDepotFiles( client.where( FileSpecBuilder.makeFileSpecList( currentPom.getCanonicalPath() ) ), false ); if ( remoteFiles.size() != 0 ) { // check for error IFileSpec fileSpec = remoteFiles.get( 0 ); if ( fileSpec.getOpStatus() == FileSpecOpStatus.VALID ) { return clientInfo.getName(); } } } } } } } catch ( Exception e ) { throw new ScmException( "Unable to discover client", e ); } return null; } private boolean clientHasTheMatchingDepotPath( IClient client, P4ScmProviderRepository repo ) { boolean found = false; for ( IClientViewMapping clientViewMapping : client.getClientView().getEntryList() ) { if ( clientViewMapping.getLeft().startsWith( repo.getPath() + "/..." ) ) { found = true; break; } } return found; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#20 | 12949 | dantran |
Long generated client name using directory path confuses perforce server. Solution: convert generated name to SHA1 hex |
||
#19 | 11530 | dantran | -add -Dverbose=true to dump p4 server messages to stdout | ||
#18 | 11522 | dantran | - Accept p4client, p4verbose, and p4jobs as system property names for easy of typing at command line | ||
#17 | 11434 | dantran |
- Allow direct perforce client name via system property to bypass the long discovery due many clients belong to an account such as at CI/Build cluster - Accomodate JENKINS-26589 |
||
#16 | 10672 | dantran | client which has multiple depot path should be discoverable | ||
#15 | 10095 | dantran |
more cannonical path handling during client discovery. Update user doc on how to use hide SCM credential using local's settings.xml |
||
#14 | 9705 | dantran |
- add apache snapshot repo for maven-scm-test-1.10-SNAPSHOT - add settings'checkStaledConnection for optimization purpose |
||
#13 | 9701 | dantran | speed up reactor build caching last IClient of current thread | ||
#12 | 9627 | dantran |
fix invalid default port. some minor cleanup |
||
#11 | 9621 | dantran | add strictClientDiscovery external setting | ||
#10 | 9602 | dantran |
shelve mojo is now working. Add basic IT |
||
#9 | 9599 | dantran | add logger to warn the created client view does not have repo path | ||
#8 | 9597 | dantran |
- minor cleanup - doc update |
||
#7 | 9566 | dantran |
- pickup charset from external config - Use repo's path during branch, if is not value use the basedir. This means it assumes maven user likely to name artifactId and module the same - Less verbose on server info |
||
#6 | 9548 | dantran |
- pickup P4PORT form env and system properties to support multi site using proxy - add development doc - check for error after each action |
||
#5 | 9543 | dantran | cleanup, pickup scm-test 1.10-snapshot again | ||
#4 | 9535 | dantran | remove staled client left off previous process run | ||
#3 | 9534 | dantran | add ClientManager test | ||
#2 | 9533 | dantran | source format | ||
#1 | 9519 | dantran | folder rename to match with its artifactId | ||
//guest/dantran/p4maven/com.perforce.maven/src/main/java/com/perforce/maven/scm/provider/p4/manager/DefaultP4ClientManager.java | |||||
#20 | 9499 | dantran |
Discover latest revision expected by scm:update goal Check for error under IFileSpec during client discovery |
||
#19 | 9498 | dantran |
- Setup Maven site for documetation - Implement ~/m2/p4maven-settings.xml to store external config overridable by system properties. All global settings now can be retrieved va P4Utils |
||
#18 | 9486 | dantran | Simplify update command and rely on AbstractUpdateCommand to gather the ChangeSet | ||
#17 | 9476 | dantran |
- Each managed client now has its own server to prevent concurrency - Validate managed client and server instances |
||
#16 | 9471 | dantran |
- cleanup diff command - simplify branch command and full compliant with maven-scm-test - add checkout with branch - add repo's path as client manager lookup key in addition to p4port and rootdir so that we can handle mutiple clients on the same rootdir |
||
#15 | 9466 | dantran | additional client discovery work to prevent false positive | ||
#14 | 9452 | dantran |
current client is now correctly discoverable. Use absolute path at ChangeLog command |
||
#13 | 9451 | dantran | Discover current client | ||
#12 | 9441 | dantran | discover existing client, so that we dont need pass in this name via system property during release:prepare | ||
#11 | 9422 | dantran | add update, changelog TCK | ||
#10 | 9276 | dantran | Implement maven scm Tag tck test. | ||
#9 | 9259 | dantran |
hookup with maven-scm-test. Pickup user/password from system properties |
||
#8 | 9250 | dantran | caching client across mutiple commands | ||
#7 | 9246 | dantran | doc a bug | ||
#6 | 9245 | dantran | typo | ||
#5 | 9244 | dantran | error handling | ||
#4 | 9243 | dantran | isolate P4JAVA IClient creation problem | ||
#3 | 9242 | dantran | implement remove generated client when done | ||
#2 | 9234 | dantran | test to makesure only on client view return | ||
#1 | 9233 | dantran | initial implementation of all the needed manager to cache Iclient and Iserver instances |