P4Java Programming
About P4Java
Perforce Software’s P4Java is a Java API that enables applications to access Perforce’s enterprise version management system in a “Java natural” and Java-native way. P4Java presents Perforce services and Perforce-managed resources and files as first-class Java interfaces, classes, methods, and objects, rather than as simple strings or command-line-style functions. This approach makes it easier to integrate the API into Java applications and tools, and is particularly useful for integrating Perforce into model-view-controller (MVC) contexts and workflows.
P4Java is aimed mostly at the following types of Java development:
- Standalone applications that need to access Perforce services from within the application
- Plug-ins for Java tools such as Eclipse, ant, Mylyn, Cruise Control, and so on, that need to communicate with Perforce servers
- J2EE applications, where P4Java can be embedded within a servlet and/or presented as a web service or an AJAX binding for client-side use
This document provides a brief guide to installing and using P4Java, and assumes a basic knowledge of both Java (JDK 5 or later) and Perforce.
System Requirements
P4Java assumes the presence of a JDK 6 or later environment, but will work against a JDK 5 installation, with some limitations.
Due to current US export control restrictions for some countries, the standard JDK package only comes with 128 bit encryption level cyphers. In order to use P4Java to connect to an SSL-enabled Perforce server, those living in eligible countries may download the unlimited strength JCE (Java Cryptography Extension) package and replace the current default cryptography jar files with the unlimited strength files.
For details, refer to the P4Java release notes.
Installation
Download the P4Java ZIP file from the Perforce web site, extract the
enclosed JARs and other files to a temporary directory, then install the
p4java.jar
JAR file into a location that is suitable for use by
compilers, JVMs, and other development tools or applications.
Documentation
Included with the P4Java ZIP file is a directory of documentation that contains this document and a full Javadoc document set for all public interfaces and classes.
The Javadoc document set can be found at:
http://www.perforce.com/perforce/r17.1/manuals/p4java-javadoc/index.html
Sample programs
Sample P4Java applications are available in Perforce’s public depot.
To access the public depot, set P4PORT
to public.perforce.com:1666
and add the depot path
//guest/perforce_software/p4java/samples/basic/...
to your client
workspace view.
These samples are used throughout this document to illustrate common usage patterns and simple code snippets, and can also be used as the basis for further user experiments with P4Java development.
Java package roadmap
The P4Java API contains the following main public packages:
com.perforce.p4java
: the mainP4Java
package hierarchy root. Contains a small handful of API-wide definitions and classes for activities like logging, tracing, and package metadata.com.perforce.p4java.server
: contains the server factory class and PerforceIServer
server interface, and associated classes and interfaces related to theIServer
definition. This package enables participating applications to connect to Perforce servers and start interacting with Perforce services through theIServer
interface.com.perforce.p4java.client
: defines the PerforceIClient
client interface and associated classes and support definitions. Participating applications typically use the IClient interface to access Perforce client services such as syncing and adding, editing, or deleting files.com.perforce.p4java.exception
: defines the main publicly-visible exceptions likely to be encountered in general use, and some specialized and rarely-encountered errors.com.perforce.p4java.core
: contains interface definitions for major Perforce-managed objects such as changelists, jobs, and clients.com.perforce.p4java.core.file
: contains the main PerforceIFileSpec
interface for accessing and defining the various types of files that Perforce manages (for example,depot
,local
, andclient
), along with associated definitions.com.perforce.p4java.impl.generic
: root package for “generic” or standard implementations of many useful Perforce client, changelist, job, and similar interfaces. These implementations are available for use by participating applications, but are not mandatory.
Basic P4Java usage model
The following basic model for P4Java reflects typical Perforce usage:
- A Java application uses the P4Java
ServerFactory
class to obtain aIServer
interface onto a specific Perforce server at a known network address and port, and connects to this Perforce server through theIServer
interface that is returned from the factory. - The application optionally logs in to the Perforce server through the
IServer
's login and associated methods. - The application obtains a suitable
IClient
interface into a Perforce client workspace through theIServer
interface’s “get client” method. - The application syncs the Perforce client workspace through the
IClient
interface’s sync method. - The application gets and processes (Java
java.util.List
) lists of depot, client, and local files in (or associated with) the Perforce client workspace, through theIServer
andIClient
interfaces. - The application adds, edits, or deletes files in the local Perforce client
workspace using the
IClient
interface. These files are added to the default or a numbered Perforce changelist represented by one or moreIChangeList
interfaces, which are obtained through theIClient
orIServer
interfaces. (There are often several ways to obtain a specific type of object depending on context, but these tend to be convenience methods rather than fundamental.) - The application submits a specific changelist using the associated
IChangeList
interface. This submission can be linked with one or more Perforce jobs, represented by theIJob
interface. - The application can switch between Perforce workspaces, browse Perforce jobs
and changelists, log in as a different user, and add, edit, or delete files,
using the relevant
IServer
orIClient
interfaces. - To disconnect from a Perforce server, the application calls the
disconnect
method on theIServer
interface.
This usage model relies heavily on representing all significant Perforce objects — clients, servers, changelists, jobs, files, revisions, labels, branches, and so on — as first-class Java interfaces, classes, or enums, and, where appropriate, returning these objects as ordered Java lists so that the developer can iterate across the results using typical Java iterator patterns. P4Java uses JDK 5 (and later) parameterized types for these lists.
P4Java represents most recoverable usage errors and Perforce errors as Java
exceptions that are subclassed out of the main P4JException
class, and thrown
from nearly every significant IServer
and IClient
interface method (and from
subsidiary and associated class methods). Most such errors are connection errors
(caused by a network or connectivity issue), access errors (caused by
permissions or authentication issues), or request errors (caused by the Perforce
server detecting a badly-constructed request or non-existent file spec). P4Java
applications catch and recover from these errors in standard ways, as discussed
in Exception and error handling.
Exceptions are not used in methods that return multiple files in lists, because the server typically interpolates errors, informational messages, and valid file specs in the same returns. P4Java provides a single method call as a standard way of identifying individual returns in the (often very long) list of returns, discussed in detail in Perforce file operations.
In general, the methods and options available on the various P4Java API
interfaces map to the basic Perforce server commands (or the familiar p4
command line equivalent), but there are exceptions. Not all Perforce server
commands are available through the P4Java API.
Unlike the Perforce C++ API or the p4
command-line client, P4Java is not
intended for direct end-user interaction. Rather, P4Java is intended to be
embedded in GUI or command-line applications to provide Perforce client / server
communications, and P4Java assumes that the surrounding context supplies the
results of user interaction directly to P4Java methods as Java objects.
Consequently, many of the environment variables used by command-line client
users (such as P4PORT
or P4USER
) are deliberately ignored by P4Java. The
values they usually represent must be explicitly set by appropriate IServer
methods or other methods.
The standard default P4Java server and client implementations are basically thread-safe. To avoid deadlock and blocking, refer to Threading issues.
Typical usage patterns
This section briefly describes typical usage patterns and provides a starting point for developers using P4Java for the first time. Many examples below are snippets from (or refer to) the P4Java sample programs available in the Perforce public depot.
To access the public depot, set P4PORT
to public.perforce.com:1666
and add
the depot path //guest/perforce_software/p4java/samples/basic/...
to your
client workspace view.
The IServer and IClient interfaces and the ServerFactory class
The com.perforce.p4java.server.IServer
interface represents a specific
Perforce server in the P4Java API, with methods to access typical Perforce
server services. Each instance of a IServer
interface is associated with a
Perforce server running at a specified location (network address and port), and
each IServer
instance is obtained from the P4Java server factory,
com.perforce.p4java.server.ServerFactory
, by passing it a suitable server URI
and optional Java properties.
The snippet below is from the ServerFactoryDemo
class in the sample package,
and shows a very simple way to prompt the user for a Perforce server URI,
connect to the server at the URI, and get basic information about that server.
This is the basic “Hello World!” P4Java application, and works like the p4
info
command (with suitable attention being paid to formatting details with
the formatInfo
method below).
BufferedReader lineReader = new BufferedReader(
new InputStreamReader(System.in));
try {
for (;;) {
System.out.print(PROMPT);
String serverUriString = lineReader.readLine();
if serverUriString == null) || serverUriString.equalsIgnoreCase("quit" {
break;
} else {
IServer server = ServerFactory.getServer(serverUriString, null);
server.connect();
IServerInfo info = server.getServerInfo();
if (info != null) {
System.out.println(
"Info from Perforce server at URI '"
+ serverUriString + "':");
System.out.println(formatInfo(info));
}
if (server != null) {
server.disconnect();
}
}
}
} catch (RequestException rexc) {
System.err.println(rexc.getDisplayString());
rexc.printStackTrace();
} catch (P4JavaException exc) {
exc.printStackTrace();
} catch (IOException ioexc) {
ioexc.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
Multiple IServer
objects can represent the same physical Perforce server, and
this approach is recommended for heavyweight usage and for multi-threaded
applications.
The Java properties parameter passed to the factory in the first example is null, but you can pass in a variety of generic and implementation-specific values as described in Character Set Support.
Perforce client workspaces are represented by the
com.perforce.p4java.client.IClient
interface, which can be used to issue
Perforce client workspace-related commands such as sync commands, file add
/delete / edit commands, and so on. A IClient
interface is typically obtained
from an IServer
interface using the getClient()
method, and is associated
with the IServer
using the setCurrentClient()
method as illustrated in the
ClientUsageDemo
snippet below:
IServer server = null;
try {
server = getServer(null);
server.setUserName(userName);
server.login(password);
IClient client = server.getClient(clientName);
if (client != null) {
server.setCurrentClient(client);
// use the client in whatever way needed...
}
} catch (Exception exc) {
// handle errors...
}
Note also the use of the setUserName
and login
methods on the server to
establish the current user and log them in, respectively.
Note also, that unlike the p4
command line client, there are no defaults for
user and workspace. Your application must explicitly associate a workspace (an
IClient
client object) and user with the server using the IServer.getClient
and IServer.setCurrentClient
methods.
Exception and error handling
P4Java uses a small set of Java exceptions to signal errors that have occurred in either the Perforce server as a result of issuing a specific command to the server, or in the P4Java plumbing in response to things like TCP/IP connection errors or system configuration issues. (These exceptions are not used to signal file operation problems at the individual file level — see Perforce file operations for details about individual file error handling.)
In general, P4Java exceptions are rooted in two different classes: the
P4JavaException
classes are intended for “normal” (that is, recoverable)
errors that occur as the result of things like missing client files, a broken
server connection, or an inappropriate command option; the P4JavaError
classes
are intended for more serious errors that are unlikely to be recoverable,
including unintended null pointers or P4Java-internal errors. The
P4JavaException
class hierarchy is rooted in the normal java.lang.Exception
tree, and any such exception is always declared in relevant method “throws”
clauses; the P4JavaError
classes, however, are rooted in java.lang.Error
,
and consequently do not need to be declared or explicitly caught. This allows a
developer to catch all such `P4JavaError`s, for example, in an outer loop, but
to process “normal” `P4JavaException`s in inner blocks and loops as they occur.
Typically, application code should report a P4JavaError
exception and then
terminate either itself or whatever it was doing as soon as possible, as this
exception indicates a serious error within P4Java. P4JavaException
handling is
more fine-grained and nuanced: A P4JavaException
almost always signals a
recoverable (or potentially-recoverable) error, and should be caught
individually or at the class level. The following snippet represents a common
pattern for P4Java
error and exception handling around major functional blocks
or processing loops:
try {
// issue one or more server or client commands...
} catch (P4JavaError err) {
panic(err); // causes app to exit after printing message to stderr...
} catch (RequestException rexc) {
// process server-side Perforce error...
} catch (ConnectionException cexc) {
// process Perforce connection exception...
} catch (P4JavaException exc) {
// catchall...
} catch (Exception exc) {
// Other-exception catchall...
}
Note the way RequestException
and ConnectionException
events are handled
separately: RequestException
exceptions are almost always thrown in response
to a Perforce server error message and therefore include a Perforce severity and
generic code that can be used or displayed (other P4JavaExceptions
do not
usually contain these), and ConnectionExceptions
should normally result in the
enclosing app explicitly closing or at least re-trying the associated
connection, as processing can no longer continue on the current Perforce
connection.
Perforce file operations
To define common Perforce-managed file attributes and options, P4Java uses the
com.perforce.p4java.core.file.IFileSpec
interface. Attributes like revisions,
dates, actions, and so on, are also defined in the core.file
package, along
with some key helper classes and methods. In general, most Perforce file-related
methods are available on the IServer
and IClient
interfaces, and might also
be available on other interfaces such as the IChangeList
interface.
Because Perforce file operations can typically run to a conclusion even with errors or warnings caused by incoming arguments, and because the server usually interpolates error and informational messages in the list of file action results being returned, most file-related methods do not throw exceptions when a request error is encountered. Instead, the file-related methods return a Java list of results, which can be scanned for errors, warnings, informational messages, and the successful file specs normally returned by the server. P4Java provides helper classes and methods to detect these errors.
P4Java file methods are also designed to be composable: the valid output of one
file method (for instance, IServer.getDepotFileList
) can usually be passed
directly to another file method (such as IClient.editFiles
) as a parameter.
This approach can be very convenient in complex contexts such as ant or Eclipse
plug-ins, which perform extensive file list processing.
The snippet below, from the sample ListFilesDemo
class, illustrates a very
common pattern used when retrieving a list of files (in this case from the
getDepotFiles
method):
List<IFileSpec> fileList = server.getDepotFiles(
FileSpecBuilder.makeFileSpecList(new String[] {"//..."}), false);
if (fileList != null) {
for (IFileSpec fileSpec : fileList) {
if (fileSpec != null) {
if (fileSpec.getOpStatus() == FileSpecOpStatus.VALID) {
System.out.println(formatFileSpec(fileSpec));
} else {
System.err.println(fileSpec.getStatusMessage());
}
}
}
}
Note in particular the use of the FileSpecBuilder.makeFileSpecList
helper
method that converts a String array to a list of IFileSpec
objects; note also
the formatFileSpec
method referenced above; this simply prints the depot path
of the returned IFileSpec
object if it’s valid.
Summary vs. Full Objects
The 2009.2 release of P4Java introduced the notion of “summary” and “full”
representations of objects on a Perforce server. In many cases the Perforce
server only returns summaries of objects that it’s been asked to list. For
example, if you issue a p4 clients
command to a server, what comes back is a
list of client metadata for known client workspaces, but not the associated
workspace views. For things like changelists, jobs, branches, and so on, to
obtain the full version of the Perforce object (such as a specific client
workspace), you typically do a p4 client -o
with the workspace’s name.
Similarly, P4Java distinguishes between the summary objects returned from the
main list methods (such as IServer.getClients()
) and the full objects returned
from individual retrieval methods (such as IServer.getClient()
).
The snippet below, edited from the ListClientDemo
sample app, illustrates a
typical usage pattern for summary and full object retrieval:
try {
IServer server = getServer(null);
server.setUserName(userName);
server.login(password);
List<IClientSummary> clientList = server.getClients(
userName, null, 0);
if (clientList != null) {
for (IClientSummary clientSummary : clientList) {
// NOTE: list returns client summaries only; need to get
// the full client to get the view:
IClient client = server.getClient(clientSummary);
System.out.println(client.getName() + " "
+ client.getDescription().trim() + " "
+ client.getRoot());
ClientView clientView = client.getClientView();
if (clientView != null) {
for (IClientViewMapping viewMapping : clientView) {
System.out.println("\t\t" + viewMapping);
}
}
}
}
} catch (RequestException rexc) {
System.err.println(rexc.getDisplayString());
rexc.printStackTrace();
} catch (P4JavaException exc) {
exc.printStackTrace();
} catch (URISyntaxException e) {
e.printStackTrace();
}
Note that only clients owned by username are returned, and that in order to print the associated client workspace view for each retrieved summary client workspace, we get the full client object. This is more common in cases where a user might iterate across a list of all workspaces known to the Perforce server in order to find a specific client workspace, then retrieve that client (and only that client) workspace in full.
Advanced usage notes
The following notes provide guidelines for developers using features beyond the basic usage model.
Perforce server addresses, URIs, and properties
P4Java uses a URI string format to specify the network location of target
Perforce servers. This URI string format is described in detail in the server
factory documentation, but it always includes at least the server’s hostname and
port number, and a scheme part that indicates a P4Java connection (for example,
p4java://localhost:1666
). Note that:
- P4Java does not obtain default values from the execution environment or other
sources for any part of the URI string. All non-optional parts of the URI must
be filled in. (For example, P4Java does not attempt to retrieve the value of
P4PORT
from a Unix or Linux environment to complete a URL with a missing port number.) - P4Java’s factory methods allow you to pass properties into the
IServer
object in the server’s URI string as query parts that override any properties that are passed in through the normal properties parameter in the server factorygetServer
method. This feature is somewhat limited in that it doesn’t currently implement URI escape sequence parsing in the query string, but it can be very convenient for properties passing. See P4Java properties for an explanation.
SSL connection support
The Perforce Server at release 2012.1 or higher supports 256-bit SSL connections and trust establishment by accepting the fingerprint of the SSL certificate’s public key.
Due to current US export control restrictions for some countries, the standard JDK package only comes with 128 bit encryption level cyphers. In order to use P4Java to connect to an SSL-enabled Perforce server, those living in eligible countries may download the unlimited strength JCE (Java Cryptography Extension) package and replace the current default cryptography jar files with the unlimited strength files.
To make a secure connection using P4Java, append ssl
to the end of the P4Java
protocol (for example, p4javassl://localhost:1667
). For a new connection or a
key change, you must also (re)establish trust using the IOptionsServer
's
addTrust
method. For example:
// Create a P4Java SSL connection to a secure Perforce server
try {
String serverUri = "p4javassl://localhost:1667";
Properties props = null;
IOptionsServer server = ServerFactory.getOptionsServer(serverUri,
props);
// assume a new first time connection
server.addTrust(new TrustOptions().setAutoAccept(true));
// if all goes well...
IServerInfo serverInfo = server.getServerInfo();
} catch (P4JavaException e) {
// process P4Java exception
} catch (Exception e) {
// process other exception
}
The IServerResource Interface
P4Java represents Perforce server objects (such as changelists, branch mappings,
job specs, and so on) to the end user through associated interfaces (such as
IChangeList
, IBranchSpec
, and so on) onto objects within P4Java that mirror
or proxy the server-side originals. This means that over time, the
P4Java-internal versions of the objects may get out of date with the server
originals, or the server originals may need to be updated with corresponding
changes made to the P4Java versions.
P4Java’s IServerResource
interface is designed to support such proxying and to
allow refreshes from the server or updates to the server as necessary. Virtually
all useful P4Java objects or interfaces that proxy or represent Perforce
server-side objects extend the IServerResource
interface, and unless otherwise
noted in the individual Javadoc comments, the interface methods can be used to
update server- and client-side versions accordingly.
P4Java properties
P4Java uses Java properties to set various operational values for specific
IServer
instances and/or for P4Java as a whole. These properties are typically
used for things like preferred temporary file directory locations, application
version and name information for Perforce server usage, and the location of a
suitable Perforce authentication tickets file (see
Authentication for details). A full list of publicly-visible
properties (with default values) is given in the PropertyDefs
Javadoc.
Properties intended for P4Java use can have “long form” or “short form”
names. Long form names are canonical, and are always prefixed by the string
represented by PropertyDefs.P4JAVA_PROP_KEY_PREFIX
(normally
com.perforce.p4java.
, for example, com.perforce.p4java.userName
). Short form
names are the same string without the standard prefix (for example, userName
).
Use long form names when there’s any chance of conflict with system or other
properties; short form names, on the other hand, are convenient for putting
property values onto URI strings as long as you know the property name won’t
collide with another property name in use by the app or system.
Properties can be passed to P4Java in several ways:
-
As properties originally passed to the JVM using the usual Java JVM and system properties mechanisms.
Passing properties in this way is useful for fundamental P4Java-wide values that do not change over the lifetime of the P4Java invocation and that do not differ from one
IServer
instance to another. A typical example of such a property is thecom.perforce.p4java.tmpDir
property, which is used by P4Java to get the name of the temporary directory to be used for P4Javatmp
files (and which defaults tojava.io.tmpdir
if not given). -
As properties passed in to an individual
IServer
instance through the server factorygetServer
method’s properties parameter.Properties passed in this way override properties passed in through the JVM. This mechanism is useful for any properties that are (or may be) server-specific, such as
userName
,clientName
, and so on. -
As properties passed in through the server factory’s URI string parameter query string.
Properties passed in this way override properties passed in through the properties parameter and the JVM. This mechanism is useful for ad hoc property-passing and/or overriding less-changeable properties passed in through the properties parameter.
The following code shows an example of passing properties to a IServer
instance using the URI string query mechanism:
IServer server = ServerFactory.getServer(
"p4java://test:1666?userName=test12&clientName=test12_client&"
+ "autoConnect=y", null);
Assuming no errors occur along the way, this code returns a IServer
object
connected to the Perforce server host test
on port 1666 with the Perforce
client name test12_client
and Perforce user name test12
logged in
automatically (note that the login only works if the underlying authentication
succeeds — see Authentication for details.
Character Set Support
Character set support is only enabled for Unicode-enabled Perforce servers. In this mode, P4Java differentiates between Perforce file content character sets (that is, the encoding used to read or write a file’s contents) and the character sets used for Perforce file names, job specs, changelist descriptions, and so on.
This distinction is made due to the way Java handles strings and basic I/O: in
general, while file content character set encodings need to be preserved so that
the end results written to or read from the local disk are properly encoded,
P4Java does not need to know about file metadata or other string value
encodings. Because Perforce servers store and transmit all such metadata and
strings in normalized UTF-8 form, and because all Java strings are inherently
encoded in UTF-16, the encoding to and from non-UTF-16 character sets (such as
shiftjis
) is done externally from P4Java (usually by the surrounding app), and
is not influenced by or implemented in P4Java itself. This means that the
character set passed to the IServer.setCharsetName
method is only used for
translation of file content. Everything else, including all file names, job
specs, changelist descriptions, and so on, is encoded in the Java-native Java
string encoding UTF-16 (and may or may not need to be translated out of that
coding to something like shiftjis
or winansi
).
P4Java supports file content operations on files encoded in most of the
character sets supported by the Perforce server, but not all. The list of
supported Perforce file content charsets is available to calling programs
through the PerforceCharsets.getKnownCharsets
method. If you attempt to set a
IServer
object’s charset to a charset not supported by both the Perforce
server and the local JDK installation, you will get an appropriate exception;
similarly, if you try to (for example) sync a file with an unsupported character
set encoding, you will also get an exception.
The Perforce server uses non-standard names for several standard character sets. P4Java also uses the Perforce version of the character set, rather than the standard name.
Error Message Localization
Error messages originating from the Perforce server are localized if the Perforce server is localized; error messages originating in P4Java itself are not currently localized. P4Java’s internal error messages aren’t intended for end-user consumption as-is; your applications should process such errors into localized versions that are presentable to end users.
Logging and tracing
P4Java includes a simple logging callback feature, documented in the
ILogCallback
Javadoc page, that enables consumers to log P4Java-internal
errors, warnings, informational messages, exceptions, and so on. Logging is
enabled or disabled on a P4Java-wide basis, not on a per-connection or
per-server basis.
The logging feature performs no message formatting or packaging. You can put the log message through the surrounding application context’s logger as required. In general, your applications should log all error and exception messages. Informational messages, statistics, and warning messages do not need to be logged unless you are working with Perforce support to debug an issue.
Standard implementation classes
The com.perforce.p4java.impl.generic
package is the root for a fairly large
set of standard implementation classes such as Job
, Changelist
, and so on.
These implementation classes are used internally by P4Java
, and while usage is
not mandatory, you are encouraged to use them as well. This is especially useful
if you wish to extend standard P4Java functionality by, for example, adding
audit or authentication methods to standard classes.
I/O and file metadata issues
The quality of P4Java’s network and file I/O in real-world usage is strongly affected by the quality of implementation of the underlying Java NIO packages. Java’s handling of file metadata also affects I/O. Although JDK 6 is an improvement over JDK 5, it can be difficult to manipulate file type and metadata (such as permissions, access/modification time, symlinks, and so on) in pure Java. These are abilities that C programmers take for granted. Issues often arise from JVM limitations such as an inability to set read-only files as writable, reset modification times, observe Unix and Linux umask values, and so on.
Because of these issues, P4Java has a file metadata helper callback scheme,
defined in com.perforce.p4java.impl.generic.sys.ISystemFileCommandsHelper
.
This approach enables users to register their own file metadata class helper
(typically using something like an Eclipse file helper or a set of native
methods) with the server factory, to help in cases where the JDK is not
sufficient. See the relevant ISystemFileCommandsHelper
Javadoc for details.
Threading issues
P4Java is inherently thread-safe when used properly. The following best practices can help to ensure that users do not encounter thread-related problems:
- P4Java’s
IServer
object is partially thread-safe. The only state preserved in the underlying implementation classes is the Perforce client that is associated with the server, and the server’s authentication state. - You can have multiple threads working against a single
IServer
object simultaneously, but note that changing authentication state (login state, password, user name, and so on) or the client that is associated with the server can have unpredictable results on long-running commands that are still running against that server object. You should ensure that changing these attributes only happens when other commands are not in progress with the particular server object. - P4Java makes no guarantees about the order of commands sent to the Perforce server by your applications. You must ensure that any required ordering is enforced.
- Using a large numbers of threads against a single
IServer
object can impose a heavy load on the JVM and the corresponding server. To control load, create your own logic for limiting thread usage. Be certain that your use of threads does not cause deadlock or blocking. Consider using a singleIServer
object for each thread. - P4Java offers a number of useful callbacks for things like logging, file helpers, progress monitoring, and so on. These callbacks are performed within a live thread context. Ensure that any callbacks that you register or use do not cause blocking or deadlocks.
- To obtain the best resource and memory allocation strategies for your specific threading needs, experiment with JVM invocation parameters. Garbage collection and memory allocation strategies can make quite a difference in raw threading throughput and latency, but often indirectly and unpredictably.
Authentication
P4Java implements both the Perforce tickets-based authentication and the Perforce single sign on (SSO) feature. Both types of authentication are described in detail in the P4Java Javadoc, but some P4Java-specific issues to note include:
-
P4Java manages a
p4 tickets
file in a matter similar to that of the P4 command line (under normal circumstances, the two can share the same tickets file). When a ticket value is requested by the Perforce server and the current ticket value in the associatedIServer
object is not set, an attempt is made to retrieve the ticket out of thep4 tickets
file. If found, the ticket is stored on theIServer
object and used as the Perforce authentication ticket.A successful login causes the ticket value to be added or updated in the tickets file, and a logout causes the current ticket value in the
p4 tickets
file to be removed. TheIServer
object’s ticket should be set tonull
to cause a re-reading of the ticket value from thep4 tickets
file.The
p4 tickets
file is usually stored in the same place thep4
command line stores it, but thePropertyDefs.TICKET_PATH_KEY
property can be used to specify an alternate tickets file. - P4Java implements Perforce’s SSO scheme using a callback interface described
in the
ISSOCallback
Javadoc (in the packagecom.perforce.p4java.server.callback
). Ensure that the callback doesn’t block, and that it adheres to the expected format of the associated Perforce server.
Other Notes
- As documented in the main Perforce documentation, Perforce form triggers can
cause additional output on form commands such as “change” or “client”,
even when the trigger succeeds. This trigger output is available through the
P4Java command callback feature, but note that there is currently no way to
differentiate trigger output from normal command output, and that such trigger
output will also be prepended or appended to normal string output on commands
such as
IServer.newLabel
. - P4Java’s command callback feature, documented in class
com.perforce.p4java.server.callback.ICommandCallback
, is a useful way to get blow-by-blow command status messages and trigger output messages from the server in a way that can mimic thep4
command line client’s output. Usage is straightforward, but note the potential for deadlocks and blocking if you are not careful with callback method implementation. - P4Java’s progress callback feature gives users a somewhat impressionistic
measure of command progress for longer-running commands. Progress callbacks
are documented in the Javadoc for class
com.perforce.p4java.server.callback.IProgressCallback
. Once again, if you use this feature, ensure that your callback implementations do not cause deadlocks or blocking. -
We strongly recommend setting the
progName
andprogVersion
properties (either globally or for eachIServer
instance) whenever you use P4Java. Set these values to something meaningful that reflects the application or tool in which P4Java is embedded; this can help Perforce administrators and application debugging.For example, the following code sets
progName
andprogVersion
via the JVM invocation property flags:$ java -Dcom.perforce.p4java.programName=p4test -Dcom.perforce.p4java.programVersion=2.01A ...
Alternatively, you can also use the server factory getServer method’s properties parameter:
Properties props = new Properties(System.getProperties());
props.setProperty(PropertyDefs.PROG_NAME_KEY, "ant-test");
props.setProperty(PropertyDefs.PROG_VERSION_KEY, "Alpha 0.9d");
...
server = IServerFactory.getServer(serverUriString, props);
-
If your application receives a
ConnectionException
from aIServer
orIClient
method while communicating with a Perforce server, the only truly safe action is to close the connection and start over with a new connection, rather than continue using the connection.A
ConnectionException
event typically represents a serious network error (such as the Perforce server unexpectedly closing a connection or a bad checksum in a network packet), and there’s no guarantee that after receiving such an event the connection is even usable, let alone reliable. -
There is currently no diff method on
IFileSpec
interfaces to compare versions of the same Perforce-managed file, but this functionality may be easily implemented with a combination ofIServer.getFileContents
to retrieve the contents of specific versions to temporary files, and the use of the operating system’s diff application on these temporary files as shown below:InputStream fspecStream1 = server.getFileContents( FileSpecBuilder.makeFileSpecList( new String[] {spec1}), false, true); InputStream fspecStream2 = server.getFileContents( FileSpecBuilder.makeFileSpecList( new String[] {spec2}), false, true); File file1 = null; File file2 = null; try { file1 = File.createTempFile("p4jdiff", ".tmp"); file2 = File.createTempFile("p4jdiff", ".tmp"); FileOutputStream outStream1 = new FileOutputStream(file1); FileOutputStream outStream2 = new FileOutputStream(file2); byte[] bytes = new byte[1024]; int bytesRead = 0; while bytesRead = fspecStream1.read(bytes > 0) { outStream1.write(bytes, 0, bytesRead); } fspecStream1.close(); outStream1.close(); while bytesRead = fspecStream2.read(bytes > 0) { outStream2.write(bytes, 0, bytesRead); } fspecStream2.close(); outStream2.close(); Process diffProc = Runtime.getRuntime().exec(new String[] { "/usr/bin/diff",file1.getPath(),file2.getPath()}); diffProc.waitFor(); if (diffProc != null) { InputStream iStream = diffProc.getInputStream(); byte[] inBytes = new byte[1024]; int inBytesRead = 0; while inBytesRead = iStream.read(inBytes > 0) { System.out.write(inBytes, 0, inBytesRead); } } } catch (Exception exc) { error("diff error: " + exc.getLocalizedMessage()); return; } finally { if (file1 != null) file1.delete(); if (file2 != null) file2.delete(); }