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.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.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 ); } catch ( P4JavaException e ) { this.getLogger().error( e.getLocalizedMessage(), e ); } } } clients.clear(); } public IClient getClient( P4ScmProviderRepository repo, String clientName, File rootDir ) throws ScmException { IClient client = null; if ( StringUtils.isBlank( clientName ) ) { clientName = this.discoverClientName( repo, rootDir ); if ( clientName == null ) { clientName = generateClientName( repo, rootDir ); } } try { InternalClient internalClient = clients.get( getClientKey( repo, clientName, rootDir ) ); if ( internalClient != null ) { client = internalClient.getClient(); } if ( client == null ) { boolean removeWhenDone = false; IServer server = serverManager.getServer( repo ); client = server.getClient( clientName ); if ( client == null ) { client = createNewClient( server, repo, clientName, rootDir ); if ( clientName.equals( this.generateClientName( repo, rootDir ) ) ) { removeWhenDone = true; } } this.adjustClientSpecDueToP4JAVAIssue( client, repo ); server.setCurrentClient( client ); // no need to do this we use absolute path at command processing // beside the next statement is unreliable since rootDir may not the same as // 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; } /** * 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 ) { if ( getLogger().isErrorEnabled() ) { this.getLogger().error( e.getMessage(), e ); } } catch ( IOException e ) { if ( getLogger().isErrorEnabled() ) { 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 = getCanonicalRepoPath( repo.getPath() ); return new ClientViewMapping( 0, viewPath, "//" + clientName + "/..." ); } /** * Gets the canonical repo path. * * @param repoPath the repo path * @return the canonical repo path */ public static String getCanonicalRepoPath( String repoPath ) { if ( repoPath == null ) { return null; } if ( repoPath.endsWith( "/..." ) ) { return repoPath; } else if ( repoPath.endsWith( "/" ) ) { return repoPath + "..."; } else { return repoPath + "/..."; } } 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 */ private 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 ); boolean found = false; for ( IClientViewMapping clientViewMapping: client.getClientView().getEntryList() ) { if ( clientViewMapping.getLeft().startsWith( repo.getPath() + "/..." ) ) { found = true; break; } } if ( !found ) { continue; } if ( baseDir.equals( new File( clientInfo.getRoot() ) ) ) { return clientInfo.getName(); } else { 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 ) { return clientInfo.getName(); } } } } } } catch ( Exception e ) { throw new ScmException( "Unable to discover client", e ); } return null; } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#21 | 9519 | dantran | folder rename to match with its artifactId | ||
#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 |