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.security.MessageDigest; import java.security.NoSuchAlgorithmException; 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 { IClient client = getClient( repo, rootDir, null ); P4ClientThreadLocal.set( client ); return client; } public IClient getClient( P4ScmProviderRepository repo, File rootDir, String clientName ) throws ScmException { IClient client = null; try { if ( StringUtils.isBlank( clientName ) ) { clientName = this.discoverClientName( repo, rootDir ); if ( clientName == null ) { clientName = generateClientName( repo, rootDir ); } } 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 ( StringUtils.isBlank( client.getRoot() ) || "null".equals( client.getRoot() ) ) { // JENKINS-26589 client.setRoot( rootDir.getAbsolutePath() ); client.update(); } if ( client.getName().equals( generateClientName( repo, rootDir ) ) ) { // this may be also what left off from previous process removeWhenDone = true; } this.adjustClientSpecIfRepoPathIsNotConfigure( 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 ( Exception e ) { throw new ScmException( e.getLocalizedMessage(), e ); } return client; } private boolean isValidClient( IClient client ) { return DefaultP4ServerManager.isValidServer( client.getServer() ); } /** * adjust client view by adding the intended entry from pom/connectionUrl if needed since the provide client may not * have it. There may be 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 adjustClientSpecIfRepoPathIsNotConfigure( 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 ) { this.getLogger().warn( "Auto adjust client view since it does not have repo path: " + repo.getPath() + " configured" ); 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 ); /* * if ( !P4Utils.isRepoPathValid( newClient, repo.getPath() ) ) { throw new P4JavaException( * "Repo path not found" + repo.getPath() ); } */ return server.getClient( clientName ); } 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 = P4Utils.encodeWildcards( basedir.getCanonicalPath().replaceAll( "[/ ~]", "-" ).replaceAll( ",", "" ) ); } catch ( IOException e ) { this.getLogger().error( e.getMessage(), e ); } String clientName = username + "-" + hostname + "-MavenSCM-" + path + "-" + StringUtils.replace( repo.getPath(), '/', '\\' ); try { return username + "-" + hostname + "-p4maven-" + toHexString( MessageDigest.getInstance( "SHA1" ).digest( clientName.getBytes() ) ); } catch ( NoSuchAlgorithmException e ) { // not possible return clientName; } } public static String toHexString( byte[] bytes ) { char[] hexArray = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] hexChars = new char[bytes.length * 2]; int v; for ( int j = 0; j < bytes.length; j++ ) { v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v / 16]; hexChars[j * 2 + 1] = hexArray[v % 16]; } return new String( hexChars ); } private 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 for an 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 { IClient client = null; try { // speed up reactor build by checking the caching IClient of at current thread local storage client = P4ClientThreadLocal.get(); if ( client != null && isClientMatched( client, repo, baseDir ) ) { // check for stale?? return client.getName(); } // pickup user provided client name, no need to go thru discovery if ( P4Utils.getSettings().getClientName() != null ) { return P4Utils.getSettings().getClientName(); } IServer server = this.serverManager.getServer( repo ); String queryString = null; if ( P4Utils.getSettings().isStrictClientDiscovery() ) { // until we know how to set the filter // queryString = "hostName=" + InetAddress.getLocalHost().getHostName(); } List<IClientSummary> clientInfos = server.getClients( repo.getUser(), queryString, Integer.MAX_VALUE ); String currentHostName = InetAddress.getLocalHost().getHostName(); for ( IClientSummary clientInfo : clientInfos ) { // quick filtering by looking for the clients with host field set to current host if ( P4Utils.getSettings().isStrictClientDiscovery() ) { String p4ClientHostName = clientInfo.getHostName(); if ( StringUtils.isBlank( p4ClientHostName ) ) { continue; } if ( !currentHostName.equals( p4ClientHostName ) ) { // see if p4 host is fqdn String[] tokens = StringUtils.split( p4ClientHostName, "." ); if ( !tokens[0].equals( currentHostName ) ) { continue; } } } client = server.getClient( clientInfo ); if ( isClientMatched( client, repo, baseDir ) ) { return client.getName(); } } } catch ( Exception e ) { throw new ScmException( "Unable to discover client", e ); } return null; } private boolean isClientMatched( IClient client, P4ScmProviderRepository repo, File baseDir ) throws P4JavaException, IOException { IServer server = client.getServer(); if ( filePathEquals( baseDir, new File( client.getRoot() ) ) ) { // one dir may be used by 2 separate clients/depo paths, like maven-scm-tck branch test if ( clientHasTheMatchingDepotPath( client, repo ) ) { return true; } } else { // see if repo's basedir is a sub-directory of an existing client File clientRootDir = new File( client.getRoot() ); if ( clientRootDir.isDirectory() && FileUtils.directoryContains( clientRootDir, baseDir ) ) { // see if the client really has this directory. 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 ); // the client spec may have multiple server paths for ( IFileSpec fileSpec : remoteFiles ) { if ( fileSpec.getOpStatus() == FileSpecOpStatus.VALID ) { // found one remote path that can host the tested local file. However, we still want to make // sure // pom has <scm> element correctly defined in itself or in its parent if ( fileSpec.getDepotPath().getPathString().equals( repo.getPath() + "/pom.xml" ) ) { return true; } } } } } } return false; } 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; } private static boolean filePathEquals( final File file1, final File file2 ) throws IOException { // Fail fast against NullPointerException if ( file1 == null ) { throw new IllegalArgumentException( "file must not be null" ); } // Fail fast against NullPointerException if ( file2 == null ) { throw new IllegalArgumentException( "file must not be null" ); } // Canonicalize paths (normalizes relative paths and softlinks) String canonicalPath1 = file1.getCanonicalPath(); String canonicalPath2 = file2.getCanonicalPath(); return canonicalPath1.equals( canonicalPath2 ); } }
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#2 | 19967 | jkovisto | Pull over numerous changes from @dantran's branch from change 16716. | ||
#1 | 10055 | Matt Attaway | Pull over numerous changes from @dantran's branch. | ||
//guest/dantran/p4maven/p4maven-provider/src/main/java/com/perforce/maven/scm/provider/p4/manager/DefaultP4ClientManager.java | |||||
#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 |