Product
Hosting Environment
Operating System
Capability
Task Type
Draft Beta
Close

PingAccess Addon SDK for Java Migration Guide

Updated 94

Add to MyDocs | Hide Show Table of Contents

PingAccess Addon SDK for Java Migration Guide

Plugins built against the Java Addon SDK for PingAccess 5.0 are now required to be built with JDK 8, as the SDK for PingAccess 5.0 uses Java 8 features. Previously, plugins could be built with either JDK 7 or JDK 8.

The following sections provide a detailed description of the changes, organized by package. Where relevant, code examples show how to port existing code to account for the changes in the SDK APIs.

General changes

Prevent modification to Request in Response chain

Starting in PingAccess 5.0, any modifications made to a Request or its header fields during response processing will now result in a warning log message and the modification operation being ignored. Previously, PingAccess would log a warning message about the modification but still allow the modification operation to complete.

Retrieving Key Pair and Trusted Certificate Group configuration data

In the previous version of the SDK, a SDK plugin accessed the configuration data of a Key Pair or Trusted Certificate Group configured via the Administrative API by annotating a field in the plugin's PluginConfiguration class with a JsonDeserialize annotation, specifying the appropriate custom deserializer from the SDK. For example:

public class Configuration extends SimplePluginConfiguration
{
   @JsonDeserialize(using = PrivateKeyDeserializer.class)   
    KeyStore.PrivateKeyEntry keyPair;

   @JsonDeserialize(using = TrustedCertificateGroupDeserializer.class)   
    Collection<X509Certificate> certificateGroup;
}

In the current version of the SDK, this mechanism has changed to be less error-prone as well as to provide access to more properties of the Key Pairs and Trusted Certificate Groups. The previous Configuration class should be ported to the following:

public class Configuration extends SimplePluginConfiguration
{
   KeyPairModel keyPair;

   TrustedCertificateGroupModel certificateGroup;
}

The KeyPairModel#getPrivateKeyEntry method provides access to the KeyStore.PrivateKeyEntry object for the corresponding Key Pair in the administrative configuration. The TrustedCertificateGroupModel#getCertificates method provides access to the Collection of X509Certificate objects in the corresponding Trusted Certificate Group in the administrative configuration. Refer to the JavaDoc for each of these classes for more information.

Related to this change, the provided implementations of ConfigurationModelAccessor, PrivateKeyAccessor and TrustedCertificateGroupAccessor, have been updated to use these new classes. Both classes have also been moved to new packages. PrivateKeyAccessor has also been renamed to KeyPairAccessor.

BEFORE:

import com.pingidentity.pa.sdk.accessor.PrivateKeyAccessor;
import com.pingidentity.pa.sdk.accessor.TrustedCertificateGroupAccessor; 

// … class definition omitted ... 

private void invokePrivateKeyAccessorGet(
       PrivateKeyAccessor accessor,
       String id)
{
   KeyStore.PrivateKeyEntry keyPair = accessor.get(id);
} 
private void invokeTrustedCertificateGroupAccessorGet(
       TrustedCertificateGroupAccessor accessor,
       String id)
{
   Collection<X509Certificate> certificates = accessor.get(id);
}

AFTER:

import com.pingidentity.pa.sdk.accessor.certgroup.TrustedCertificateGroupModel;
import com.pingidentity.pa.sdk.accessor.keypair.KeyPairAccessor; 

// … class definition omitted ... 

private void invokePrivateKeyAccessorGet(
       KeyPairAccessor accessor,
       String id)
{
   KeyStore.PrivateKeyEntry keyPair = accessor.get(id)
                                              .map(KeyPairModel::getPrivateKeyEntry)
                                              .orElse(null);
} 

private void invokeTrustedCertificateGroupAccessorGet(
       TrustedCertificateGroupAccessor accessor,
       String id)
{
   Collection<X509Certificate> certificates = accessor.get(id)
                   .map(TrustedCertificateGroupModel::getCertificates)
                   .orElse(null);
}

Changes to validation of PluginConfiguration instances

In the previous version of the SDK, the ConfigurablePlugin#configure method was invoked and passed a PluginConfiguration instance. The ConfigurablePlugin was expected to assign the specified PluginConfiguration instance to a field annotated with the javax.validation.Valid annotation. After the configure method returned, PingAccess passed the ConfigurablePlugin instance to a javax.validation.Validator for further validation.

If setup correctly, this logic allows javax.validation.Constraint annotations to be used to declare the validation to be applied to fields in a PluginConfiguration implementation, ensuring the configuration was valid as well as providing validation error message to PingAccess to provide to administrators using the Administrative API or UI.

However, if the ConfigurablePlugin#configure method needed to post-process the specified PluginConfiguration instance, the method needed to duplicate all the validation declared on the fields of the PluginConfiguration.

To remove the need for this duplication of validation logic, PingAccess will now validate the PluginConfiguration instance with a javax.validation.Validator prior to passing the instance to the ConfigurablePlugin#configure method.

Further, the ConfigurablePlugin no longer needs to annotate the field used to hold the PluginConfiguration instance. The field is still necessary to implement the ConfigurablePlugin#getConfiguration method.

The following example ConfigurablePlugin implementation demonstrates this change:

public class ValidationExample
       implements ConfigurablePlugin<ValidationExample.Configuration>
{
   // @Valid annotation no longer required
   private Configuration configuration;

   @Override
   public void configure(Configuration configuration) throws ValidationException
   {
       this.configuration = configuration;

       // With the previous version of the SDK, these assertions were not
       // guaranteed to be true, despite the javax.validation.Constraint
       // annotations enforcing these conditions.
       //
       // In the current version of the SDK, these assertions are guaranteed
       // to be true because they are enforced by the javax.validation.Constraint
       // annotations on the fields in the PluginConfiguration class, and the
       // PluginConfiguration validation is performed before invoking the
       // configure method.
       //
       // The end result is that plugins can remove duplicated validation
       // logic from the configure method if further post-processing of the
       // configuration needs to be performed.
       assert(configuration.getAttributeName() != null);
       assert(configuration.getAttributeName().length() > 0);
       assert(configuration.getAttributeName().length() <= 16);
       assert(configuration.getAttributeValue() != null);

   }

   @Override
   public Configuration getConfiguration()
   {
       return configuration;
   }

   static class Configuration extends SimplePluginConfiguration
   {
       @NotNull
       @Size(min = 1,
             max = 16,
             message = "Attribute name length must be between 1 and 16 characters")
       private String attributeName;

       @NotNull
       private String attributeValue;

       public String getAttributeName()
       {
           return attributeName;
       }

       public void setAttributeName(String attributeName)
       {
           this.attributeName = attributeName;
       }

       public String getAttributeValue()
       {
           return attributeValue;
       }

       public void setAttributeValue(String attributeValue)
       {
           this.attributeValue = attributeValue;
       }
   }
}

com.pingidentity.pa.sdk.http

com.pingidentity.pa.sdk.http.Body

The Body interface has changed to require an explicit read of data before invoking methods to obtain that data. Previously, methods to obtain the data would result in an implicit read of the data. The following code examples illustrate this change in semantics.

As the updated JavaDoc for the Body interface indicates, plugins should avoid interrogating a Body object unless absolutely necessary because reading a Body object's data into memory can impact the scalability of PingAccess. As plugin code is updated, evaluate whether the Body object needs to be used by the plugin.

Using the Body#read method

BEFORE:

private void invokeRead(Body body) throws IOException
{
    body.read();
}

AFTER:

private void invokeRead(Body body) throws AccessException
{
    try
    {
        body.read();
    }
    catch (IOException e)
    {
        throw new AccessException("Failed to read body content",
                                  HttpStatus.BAD_GATEWAY,
                                  e);
    }
}

Using the Body#getContent method:

BEFORE:

private void invokeGetContent(Body body) throws IOException
{
    byte[] content = body.getContent();
}

AFTER:

private void invokeGetContent(Body body) throws AccessException
{
    invokeRead(body); // see the Body#read code example for this method
    byte[] content = body.getContent();
}

Using the Body#getBodyAsStream method:

BEFORE:

private void invokeGetBodyAsStream(Body body) throws IOException
{
    InputStream stream = body.getBodyAsStream();
}

AFTER:

private void invokeGetBodyAsStream(Body body) throws AccessException
{
    invokeRead(body); // see the Body#read code example for this method
    InputStream stream = body.newInputStream();
}

Note the rename of the method from getBodyAsStream to newInputStream.

Using the Body#write method:

BEFORE:

private void invokeWrite(Body body, BodyTransferrer bodyTransferrer) throws IOException

{

    body.write(bodyTransferrer);

}

AFTER:

This functionality is no longer supported. To obtain the content of the Body, read the content into memory using the Body#read method and then invoke Body#getContent or Body#newInputStream.

Using the Body#getLength method

BEFORE:

private void invokeGetLength(Body body) throws IOException
{
    int length = body.getLength();
}

AFTER:

private void invokeGetLength(Body body) throws AccessException

{

    invokeRead(body); // see the Body#read code example for this method

    int length = body.getLength();

}

Using the Body#getRaw method:

BEFORE:

private void invokeGetRaw(Body body) throws IOException
{
    byte[] rawBody = body.getRaw();
}

AFTER:

This functionality is no longer supported. This method used to provide access to the content as it appeared on the wire, which required complicated handling if the body content used a chunked Transfer-Encoding. Use Body#getContent instead.

com.pingidentity.pa.sdk.http.BodyFactory

Using the BodyFactory#continuousBody method

BEFORE:

private void invokeContinuousBody(BodyFactory bodyFactory, byte[] content)
{
    Body body = bodyFactory.continuousBody(content);
}

AFTER:

private void invokeContinuousBody(BodyFactory bodyFactory, byte[] content)
{
    Body body = bodyFactory.createInMemoryBody(content);
}

BEFORE:

private void invokeContinuousBody(BodyFactory bodyFactory, InputStream in)
{
    Body body = bodyFactory.continuousBody(in);
}

AFTER:

A Body instance can no longer be created from an InputStream using the BodyFactory class. Instead, a plugin should read the contents of the InputStream into a byte array and provide the byte array to BodyFactory#createInMemoryBody.

com.pingidentity.pa.sdk.http.Constants

The constants available from this class have been removed from the SDK. Plugins using these constants should maintain their own constants with the needed values.

com.pingidentity.pa.sdk.http.Exchange

A handful of methods have been removed from the Exchange.

Further, the mechanism for storing data on the Exchange via properties has been enhanced to make it easier to write type-safe code when working with Exchange properties.

Using the Exchange#getCreationTime method

BEFORE:

Calendar creationTime = exchange.getCreationTime();

AFTER:

Calendar creationTime = Calendar.getInstance();
creationTime.setTime(Date.from(exchange.getCreationTime()));

NOTE: If a Calendar object is not required, consider using the Instant object returned from the getCreationTime method directly instead of converting it into a Calendar object.

Using the Exchange#getDestinations method

BEFORE:

List<String> destinations = exchange.getDestinations();

AFTER:

This functionality is no longer supported. Consider using the Exchange#getTargetHosts method to obtain similar information from the Exchange.

Using the Exchange#getOriginalHostHeader method

BEFORE:

String originalHostHeader = exchange.getOriginalHostHeader();

AFTER:

This functionality is no longer supported. Consider using the Exchange#getUserAgentHost method to obtain similar information from the Exchange. The getUserAgentHost method leverages the PingAccess HTTP requests configuration to determine the Host header value sent by the user agent.

Using the Exchange#getOriginalHostHeaderHost method

BEFORE:

String host = exchange.getOriginalHostHeaderHost();

AFTER:

This functionality is no longer supported. Consider using the Exchange#getUserAgentHost method to obtain similar information from the Exchange. The getUserAgentHost method leverages the PingAccess HTTP requests configuration to determine the Host header value sent by the user agent.

Using the Exchange#getOriginalHostHeaderPort method

BEFORE:

String port = exchange.getOriginalHostHeaderPort();

AFTER:

This functionality is no longer supported. Consider using the Exchange#getUserAgentHost method to obtain similar information from the Exchange. The getUserAgentHost method leverages the PingAccess HTTP requests configuration to determine the Host header value sent by the user agent.

Using the Exchange#getOriginalRequestBaseUri method

BEFORE:

String originalRequestBaseUri = exchange.getOriginalRequestBaseUri();

AFTER:

This functionality is no longer supported. A possible replacement is as follows:

String originalRequestBaseUri = exchange.getUserAgentProtocol() +
                                "://" +
                                exchange.getUserAgentHost();

Using the Exchange#getProperties method

BEFORE:

Map<String, String> properties = exchange.getProperties();

AFTER:

This functionality is no longer supported. Properties should be obtained individually from the Exchange.

Using the Exchange#getRequestBaseUri method

BEFORE:

String requestBaseUri = exchange.getRequestBaseUri();

AFTER:

This functionality is no longer supported. A possible replacement is as follows:

String requestBaseUri = exchange.getUserAgentProtocol() +
                        "://" +
                        exchange.getUserAgentHost();

Using the Exchange#getRequestScheme method

BEFORE:

String requestScheme = exchange.getRequestScheme();

AFTER:

This functionality is no longer supported. A possible replacement is as follows:

String requestScheme = exchange.getUserAgentProtocol() + "://";

Using the Exchange#getUser method

BEFORE:

User user = exchange.getUser();

AFTER:

The User interface is no longer supported. Use the Identity interface instead. It can be retrieved via the Exchange#getIdentity method.

Using the Exchange#setUser method

BEFORE:

private void invokeSetUser(Exchange exchange, User user)
{
    exchange.setUser(user);
}

AFTER:

This functionality is no longer supported. The identity associated with an Exchange cannot be replaced.

Using the Exchange#setSourceIp method

BEFORE:

private void invokeSetSourceIp(Exchange exchange, String sourceIp)
{
    exchange.setSourceIp(sourceIp);
}

AFTER:

This functionality is no longer supported. This value cannot be changed.

Using the Exchange#setProperty method

BEFORE:

private void invokeSetProperty(Exchange exchange, String propertyKey, String value)
{
    exchange.setProperty(propertyKey, value);
}

AFTER:

private void invokeSetProperty(Exchange exchange,
                               ExchangeProperty<String> propertyKey,
                               String value)
{
    exchange.setProperty(propertyKey, value);
}

See the JavaDoc for ExchangeProperty for instructions on creating an ExchangeProperty object.

Using the Exchange#getProperty method

BEFORE:

private void invokeGetProperty(Exchange exchange, String propertyKey)
{
    Object propertyValueObj = exchange.getProperty(propertyKey);
    if (propertyValueObj instanceof String)
    {
        String propertyValue = (String) propertyValueObj;
    }
}

AFTER:

private void invokeGetProperty(Exchange exchange, ExchangeProperty<String> propertyKey)
{
    String propertyValue = exchange.getProperty(propertyKey).orElse(null);
}

NOTE: Exchange#getProperty now returns an Optional object instead of the Object directly.

com.pingidentity.pa.sdk.http.Header

This deprecated class has been replaced by the Headers interface. A Headers object can be created via a HeadersFactory obtained from the ServiceFactory#headersFactory method. The majority of methods on Header have counterparts on the Headers interface. See the JavaDoc for the Headers interface for more information.

com.pingidentity.pa.sdk.http.HeaderField

This class is now final and cannot be extended.

Constructing a HeaderField

BEFORE:

private HeaderField createHeaderField(String line)
{
    return new HeaderField(line);
}

AFTER:

private HeaderField createHeaderField(String line)
{
    String name = line.substring(0, line.indexOf(':'));
    String value = (line.substring(line.indexOf(":") + 1)).trim();

    return new HeaderField(name, value);
}

NOTE: Parsing an HTTP header field line can be error prone, consider if the plugin can be avoid having to parse an HTTP header field line.

Using the HeaderField#setHeaderName method:

BEFORE:

private void invokeSetHeaderName(HeaderField field)
{
    field.setHeaderName(new HeaderName("X-Custom"));
}

AFTER:

This functionality is no longer supported. A HeaderField's name is set upon construction and cannot be changed.

Using the HeaderField#getApproximateSize method:

BEFORE:

int approximateSize = field.getApproximateSize();

AFTER:

This method has been removed. The value returned by the method can still be computed:

int approximateSize = 2 * (4 +
                           field.getHeaderName().toString().length() +
                           field.getValue().length());
com.pingidentity.pa.sdk.http.Headers

A few methods on the Headers interface have been updated to use the Instant class, instead of Date.

Using the Headers#getDate method

BEFORE:

Date date = headers.getDate();

AFTER:

Date date = Date.from(headers.getDate());

Using the Headers#setDate method

BEFORE:

private void invokeSetDate(Headers headers, Date date)
{
    headers.setDate(date);
}

AFTER:

private void invokeSetDate(Headers headers, Date date)
{
    headers.setDate(date.toInstant());
}

Using the Headers#getLastModified method

BEFORE:

SimpleDateFormat format = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z",
                                               Locale.ENGLISH);

String lastModified = headers.getLastModified();
if (lastModified != null)
{
    Date lastModifiedDate = format.parse(lastModified);
}

AFTER:

Date lastModifiedDate = Date.from(headers.getLastModified());

Using the Headers#setLastModified method

BEFORE:

private void invokeSetLastModified(Headers headers, Date date)
{
    SimpleDateFormat format = new SimpleDateFormat("E, dd MMM yyyy HH:mm:ss z",
                                                   Locale.ENGLISH);

    headers.setLastModified(format.format(date));
}

AFTER:

private void invokeSetLastModified(Headers headers, Date date)
{
    headers.setLastModified(date.toInstant());
}
com.pingidentity.pa.sdk.http.HeadersFactory

Using the HeadersFactory#createFromRawHeaderFields method

BEFORE:

private void invokeCreateFromRawHeaderFields(HeadersFactory factory,
                                             List<String> fields) throws ParseException
{
    Headers headers = factory.createFromRawHeaderFields(fields);
}

AFTER:

This functionality is no longer supported. Consider if the plugin can create HeaderFields directly and utilize the HeadersFactory#create method.

com.pingidentity.pa.sdk.http.HttpStatus

The HttpStatus enum was converted to a final class. Common HttpStatus instances are defined as constants on HttpStatus.

Using the HttpStatus#getLocalizationKey method

BEFORE:

String localizationKey = status.getLocalizationKey();

AFTER:

This functionality is no longer supported. Instead, a HttpStatus contains a LocalizedMessage instance that encapsulates the localization of the status message for use in error templates.

com.pingidentity.pa.sdk.http.MimeType

The constants available in this class are now available as constant MediaType instances in the class com.pingidentity.pa.sdk.http.CommonMediaTypes.

com.pingidentity.pa.sdk.http.MediaType

This class is now final and cannot be extended.

Constructing a MediaType

BEFORE:

private void createMediaType(String mediaTypeString)
{
    MediaType mediaType = new MediaType(mediaTypeString);
}

AFTER:

private void createMediaType(String mediaTypeString)
{
    MediaType mediaType = MediaType.parse(mediaTypeString);
}
com.pingidentity.pa.sdk.http.Message

A number of methods have been removed from the Message interface.

Using the Message#getBodyAsStream method

BEFORE:

InputStream bodyStream = message.getBodyAsStream();

AFTER:

This functionality is no longer supported. However, the following code snippet could be used to maintain semantics of the old method.

Body body = message.getBody();
try
{
    body.read();
}
catch (IOException | AccessException e)
{
    throw new RuntimeException("Could not get body as stream", e);
}

InputStream bodyStream = body.newInputStream();

While this snippet maintains semantics, it is recommended that a plugin propagate errors as an AccessException instead of a RuntimeException.

Using the Message#getCharset method

BEFORE:

Charset charset = message.getCharset();

AFTER:

This functionality is no longer supported. However, the following code snippet could be used to maintain semantics of the old method.

Charset charset = message.getHeaders().getCharset();
if (charset == null)
{
    charset = StandardCharsets.UTF_8;
}

While this snippet maintains semantics, a plugin should consider how to handle the case where a Charset is not specified by a Message's header fields. Assuming a Charset of UTF-8 could lead to issues in some cases.

Using the Message#getHeader method

BEFORE:

Header header = message.getHeader();

AFTER:

This functionality is no longer supported. Instead, use Message#getHeaders and the Headers interface instead of Header.

Using the Message#setHeader method

BEFORE:

private void invokeSetHeader(Message message, Header header)
{
    message.setHeader(header);
}

AFTER:

This functionality is no longer supported. Instead, use Message#setHeaders and the Headers interface instead of Header.

Using the Message#isDeflate method

BEFORE:

boolean deflate = message.isDeflate();

AFTER:

This method has been removed. However, the value can still be computed with the following code snippet:

List<String> contentEncodingValues = message.getHeaders().getContentEncoding();
boolean deflate = contentEncodingValues.stream().anyMatch(v -> v.equalsIgnoreCase("deflate"))
                  && contentEncodingValues.size() == 1;

Using the Message#isGzip method

BEFORE:

boolean gzip = message.isGzip();

AFTER:

This method has been removed. However, the value can still be computed with the following code snippet:

List<String> contentEncodingValues = message.getHeaders().getContentEncoding();
boolean gzip = contentEncodingValues.stream().anyMatch(v -> v.equalsIgnoreCase("gzip"))
               && contentEncodingValues.size() == 1;

Using the Message#isHTTP10 method

BEFORE:

boolean http10 = message.isHTTP10();

AFTER:

This method has been removed. However, the value can still be computed with the following code snippet:

boolean http10 = message.getVersion().equals("1.0");

Using the Message#isHTTP11 method

BEFORE:

boolean http11 = message.isHTTP11();

AFTER:

The method has been removed. However, the value can still be computed with the following code snippet:

boolean http11 = message.getVersion().equals("1.1");

Using the Message#read method

BEFORE:

private void invokeRead(Message message,
                        InputStream inputStream,
                        boolean createBody) throws IOException
{
    message.read(inputStream, createBody);
}

AFTER:

This functionality is no longer supported. A Request attached to an Exchange can no longer be completely replaced, but individual components can be replaced, such as the method, uri, headers and body. A Response attached to an Exchange can be replaced by using Exchange#setResponse.

Using the Message#setVersion method

BEFORE:

private void invokeSetVersion(Message message, String version)
{
    message.setVersion(version);
}

AFTER:

This functionality is no longer supported. The version of a Message cannot be changed.

Using the Message#write method

BEFORE:

private void invokeWrite(Message message,
                         OutputStream output) throws IOException
{
    message.write(output);
}

AFTER:

This functionality is no longer supported. However, the following code snippet can be used to perform the equivalent operation:

private void invokeWrite(Message message,
                         OutputStream output) throws IOException, AccessException
{
    Body body = message.getBody();
    body.read();

    output.write(message.getStartLine().getBytes(StandardCharsets.ISO_8859_1));
    output.write(message.getHeaders().toString().getBytes(StandardCharsets.ISO_8859_1));
    output.write("\r\n".getBytes(StandardCharsets.ISO_8859_1));
    output.write(body.getContent());
    output.flush();
}
com.pingidentity.pa.sdk.http.Method

The Method interface has been converted to a final class. Additionally, the related Methods enum has been merged into the Method class. The Method class provides common Method instances as class-level constants.

Obtaining a common Method instance

BEFORE:

Method get = Methods.GET

AFTER:

Method get = Method.GET;

Using the Method#getMethodName method

BEFORE:

String methodName = method.getMethodName();

AFTER:

String methodName = method.getName();
com.pingidentity.pa.sdk.http.Request

A few methods have been removed from the Request interface.

Using the Request#getPostParams method

BEFORE:

private void invokeGetPostParams(Request request) throws IOException
{
    Map<String, String[]> postParams = request.getPostParams();
}

AFTER:

private void invokeGetPostParams(Request request) throws AccessException
{
    Body body = request.getBody();
    try
    {
        body.read();
    }
    catch (IOException e)
    {
        throw new AccessException("Failed to read body content",
                                  HttpStatus.BAD_GATEWAY,
                                  e);
    }

    Map<String, String[]> postParams = body.parseFormParams();
}

Using the Request#isMultipartFormPost method

BEFORE:

boolean multipartFormPost = request.isMultipartFormPost();

AFTER:

This method has been removed from the Request interface. However, the value can still be calculated using the following code snippet:

Headers headers = request.getHeaders();

boolean multipartFormPost =
        request.getMethod() == Method.POST
        && headers.getContentType() != null
        && headers.getContentType().getBaseType().equals("multipart/form-data")
        && headers.getContentType().getParameter("boundary") != null;
com.pingidentity.pa.sdk.http.ResponseBuilder

A handful of methods were removed from ResponseBuilder. Additionally, a handful of methods have changed their semantics, particularly those that included an HTML message payload. See the updated JavaDoc for ResponseBuilder for more info.

Using the ResponseBuilder#badRequestText method

BEFORE:

Response response = ResponseBuilder.badRequestText(message).build();

AFTER:

Response response = ResponseBuilder.newInstance(HttpStatus.BAD_REQUEST)
                                   .contentType(CommonMediaTypes.TEXT_PLAIN)
                                   .body(message)
                                   .build();

NOTE: This approach does not localize the response body. Using a TemplateRenderer is recommended instead.

Using the ResponseBuilder#contentLength method

BEFORE:

Response response = ResponseBuilder.newInstance().contentLength(length).build();

AFTER:

This functionality is no longer supported. Consider using one of the ResponseBuilder#body methods instead of explicitly setting the content length. This ensures that the body content of the Response aligns with the Content-Length header field.

Using the ResponseBuilder#continue100 method

BEFORE:

Response response = ResponseBuilder.continue100().build();

AFTER:

Response response = ResponseBuilder.newInstance(HttpStatus.CONTINUE).build();

Using the ResponseBuilder#forbiddenText method

BEFORE:

Response response = ResponseBuilder.forbiddenText().build();

AFTER:

Response response = ResponseBuilder.newInstance(HttpStatus.FORBIDDEN)
                                   .contentType(CommonMediaTypes.TEXT_PLAIN)
                                   .body(HttpStatus.FORBIDDEN.getMessage())
                                   .build();

NOTE: This approach does not localize the response body. Using a TemplateRenderer is recommended instead.

Using the ResponseBuilder#forbiddenWithoutBody method

BEFORE:

Response response = ResponseBuilder.forbiddenWithoutBody().build();

AFTER:

Response response = ResponseBuilder.newInstance(HttpStatus.FORBIDDEN).build();

BEFORE:

Response response = ResponseBuilder.forbiddenWithoutBody(message).build();

AFTER:

Response response = ResponseBuilder.newInstance(HttpStatus.FORBIDDEN).build();

NOTE: In the original method, the String message parameter was not used.

Using the ResponseBuilder#htmlMessage method

BEFORE:

String message = ResponseBuilder.htmlMessage(caption, text);

AFTER:

This functionality is no longer supported. Plugins that used this method will need to construct the HTML message without this method. Consider using the TemplateRenderer utility class in place of this method.

Using the ResponseBuilder#internalServerError method

BEFORE:

Response response = ResponseBuilder.internalServerError(message).build();

AFTER:

Response response = ResponseBuilder.internalServerError().body(message).build();

NOTE: This approach does not localize the response body. Using a TemplateRenderer is recommended instead.

Using the ResponseBuilder#internalServerErrorWithoutBody method

BEFORE:

Response response = ResponseBuilder.internalServerErrorWithoutBody().build();

AFTER:

Response response = ResponseBuilder.internalServerError().build();

Using the ResponseBuilder#newInstance method

The no-arg newInstance method has been removed. A HttpStatus is required to create an instance of ResponseBuilder and the required HttpStatus object should be passed to the newInstance method that accepts a HttpStatus.

BEFORE:

Response response = ResponseBuilder.newInstance().build()

AFTER:

Response response = ResponseBuilder.newInstance(HttpStatus.INTERNAL_SERVER_ERROR).build();

Using the ResponseBuilder#noContent method

BEFORE:

Response response = ResponseBuilder.noContent().build();

AFTER:

Response response = ResponseBuilder.newInstance(HttpStatus.NO_CONTENT).build();

Using the ResponseBuilder#notFoundWithoutBody method

BEFORE:

Response response = ResponseBuilder.notFoundWithoutBody().build();

AFTER:

Response response = ResponseBuilder.notFound().build();

Using the ResponseBuilder#serverUnavailable method

BEFORE:

Response response = ResponseBuilder.serverUnavailable(message).build();

AFTER:

Response response = ResponseBuilder.serviceUnavailable().body(message).build();

NOTE: This approach does not localize the response body. Using a TemplateRenderer is recommended instead.

Using the ResponseBuilder#serviceUnavailableWithoutBody method

BEFORE:

Response response = ResponseBuilder.serverUnavailableWithoutBody().build();

AFTER:

Response response = ResponseBuilder.serviceUnavailable().build();

Using the ResponseBuilder#status method

The status methods have been removed. Instead the status should be specified to the newInstance method as it is now required.

BEFORE:

Response response = ResponseBuilder.newInstance().status(HttpStatus.OK).build();

AFTER:

Response response = ResponseBuilder.newInstance(HttpStatus.OK).build();

Using the ResponseBuilder#unauthorizedWithoutBody method

BEFORE:

Response response = ResponseBuilder.unauthorizedWithoutBody().build();

AFTER:

Response response = ResponseBuilder.unauthorized().build();
com.pingidentity.pa.sdk.http.Response

A few methods were removed from the Response interface.

Using the Response#isRedirect method

BEFORE:

boolean redirect = response.isRedirect();

AFTER:

boolean redirect = response.getStatusCode() >= 300 
                   && response.getStatusCode() < 400;

Using the Response#setStatusCode method

BEFORE:

response.setStatusCode(HttpStatus.OK.getCode());

AFTER:

response.setStatus(HttpStatus.OK);

Using the Response#setStatusMessage method

BEFORE:

response.setStatusMessage(HttpStatus.OK.getMessage());

AFTER:

response.setStatus(HttpStatus.OK);

com.pingidentity.pa.sdk.identity

com.pingidentity.pa.sdk.identity.Identity

The getTokenExpiration method was updated to use an Instant instead of Date.

Using the Identity#getTokenExpiration method

BEFORE:

Date expiration = identity.getTokenExpiration();

AFTER:

Date expiration = Date.from(identity.getTokenExpiration());
com.pingidentity.pa.sdk.identity.OAuthTokenMetadata

The OAuthTokenMetadata methods now use an Instant instead of a Date.

Using the OAuthTokenMetadata#getExpiresAt method:

BEFORE:

Date expiresAt = metadata.getExpiresAt();

AFTER:

Date expiresAt = Date.from(metadata.getExpiresAt());

Using the OAuthTokenMetadata#getRetrievedAt method:

BEFORE:

Date retrievedAt = metadata.getRetrievedAt();

AFTER:

Date retrievedAt = Date.from(metadata.getRetrievedAt());

com.pingidentity.pa.sdk.identitymapping.header

ClientCertificateMapping has been removed from the SDK, as it was not required to create an IdentityMappingPlugin implementation.

Plugins utilizing this class should create their own version of this class.

com.pingidentity.pa.sdk.policy

com.pingidentity.pa.sdk.policy.AccessExceptionContext

The nested Builder class has been removed from AccessExceptionContext and instead AccessExceptionContext is a builder, that can be initially created with the new AccessExceptionContext#create method.

The LocalizedMessage interface has been introduced to simplify the configuration of a localized message for use in an error template. A LocalizedMessage has three implementations provided in the SDK: FixedMessage, BasicLocalizedMessage and ParameterizedLocalizedMessage. See the following code examples for more information on using these new classes.

Constructing an AccessExceptionContext:

BEFORE:

private AccessExceptionContext createAccessExceptionContext(HttpStatus httpStatus,
                                                            Throwable cause)

{
    return AccessExceptionContext.builder()
                                 .cause(cause)
                                 .httpStatus(httpStatus)
                                 .exceptionMessage(httpStatus.getMessage())
                                 .errorDescription(httpStatus.getLocalizationKey())
                                 .errorDescriptionIsKey(true)
                                 .errorDescriptionSubstitutions(new String[0])
                                 .build();
}

AFTER:

private AccessExceptionContext createAccessExceptionContext(HttpStatus httpStatus,
                                                            Throwable cause)
{
    return AccessExceptionContext.create(httpStatus)
                                 .cause(cause)
                                 .exceptionMessage(httpStatus.getMessage())
                                 .errorDescription(httpStatus.getLocalizedMessage());
}

BEFORE:

private AccessExceptionContext createAccessExceptionContext(HttpStatus httpStatus,
                                                            String localizationKey,
                                                            String[] substitutions)
{
    return AccessExceptionContext.builder()
                                 .httpStatus(httpStatus)
                                 .errorDescription(localizationKey)
                                 .errorDescriptionIsKey(true)
                                 .errorDescriptionSubstitutions(substitutions)
                                 .build();
}

AFTER:

private AccessExceptionContext createAccessExceptionContext(HttpStatus httpStatus,
                                                            String localizationKey,
                                                            String[] substitutions)
{
    LocalizedMessage localizedMessage =
            new ParameterizedLocalizedMessage(localizationKey, substitutions);

    return AccessExceptionContext.create(httpStatus)
                                 .errorDescription(localizedMessage);
}

BEFORE:

private AccessExceptionContext createAccessExceptionContext(HttpStatus httpStatus,
                                                            String localizationKey)
{
    return AccessExceptionContext.builder()
                                 .httpStatus(httpStatus)
                                 .errorDescription(localizationKey)
                                 .errorDescriptionIsKey(true)
                                 .build();
}

AFTER:

private AccessExceptionContext createAccessExceptionContext(HttpStatus httpStatus,
                                                            String localizationKey)
{
    LocalizedMessage localizedMessage = new BasicLocalizedMessage(localizationKey);
    return AccessExceptionContext.create(httpStatus)
                                 .errorDescription(localizedMessage);
}

BEFORE:

private AccessExceptionContext createAccessExceptionContext(HttpStatus httpStatus)
{
    return AccessExceptionContext.builder()
                                 .from(httpStatus)
                                 .httpStatusDescription(httpStatus.getLocalizationKey())
                                 .httpStatusDescriptionIsKey(true)
                                 .templateFile("template.html")
                                 .contentType("text/html");
}

AFTER:

private AccessExceptionContext createAccessExceptionContext(HttpStatus httpStatus)
{
    return AccessExceptionContext.create(httpStatus)
                                 .errorDescription(httpStatus.getLocalizedMessage());
}

NOTE: this example demonstrates that it is no longer possible to set a template file and its associated content type on an AccessExceptionContext. To generate an error response from a template file, use the TemplateRenderer class. See the JavaDoc for the TemplateRenderer class for more information.

com.pingidentity.pa.sdk.policy.AccessException

The changes to AccessExceptionContext apply to the creation of AccessException because the creation of an AccessException requires an AccessExceptionContext.

In addition to these changes, obtaining information from AccessException has also changed. See the code examples below for more information.

Finally, AccessException no longer derives from IOException and derives directly from Exception instead.

Constructing an AccessException:

BEFORE:

private void throwAccessException(String errorDescription,
                                  Throwable throwable) throws AccessException
{
    throw new AccessException(errorDescription, throwable);
}

AFTER:

private void throwAccessException(String errorDescription,
                                  Throwable throwable) throws AccessException
{
    LocalizedMessage templateMessaage = new FixedMessage(errorDescription);
    throw new AccessException(AccessExceptionContext.create(HttpStatus.INTERNAL_SERVER_ERROR)
                                                    .exceptionMessage(errorDescription)
                                                    .cause(throwable)
                                                    .errorDescription(templateMessaage));
}

BEFORE:

private void throwAccessException(String errorDescription) throws AccessException
{
    throw new AccessException(errorDescription);
}

AFTER:

private void throwAccessException(String errorDescription) throws AccessException
{
    LocalizedMessage templateMessage = new FixedMessage(errorDescription);
    throw new AccessException(AccessExceptionContext.create(HttpStatus.INTERNAL_SERVER_ERROR)
                                                    .exceptionMessage(errorDescription)
                                                    .errorDescription(templateMessage));
}

BEFORE:

private void createAccessException(int statusCode,
                                   String statusMessage,
                                   String errorDescription) throws AccessException
{
    throw new AccessException(statusCode, statusMessage, errorDescription);
}

AFTER:

private void createAccessException(int statusCode,
                                   String statusMessage,
                                   String errorDescription) throws AccessException
{
    HttpStatus httpStatus = new HttpStatus(statusCode, statusMessage);
    LocalizedMessage templateMessage = new FixedMessage(errorDescription);
    throw new AccessException(AccessExceptionContext.create(httpStatus)
                                                    .exceptionMessage(errorDescription)
                                                    .errorDescription(templateMessage));
}

BEFORE:

private void throwAccessException(int statusCode,
                                  String statusMessage,
                                  String errorDescription,
                                  Throwable throwable) throws AccessException
{
    throw new AccessException(statusCode, statusMessage, errorDescription, throwable);
}

AFTER:

private void throwAccessException(int statusCode,
                                  String statusMessage,
                                  String errorDescription,
                                  Throwable throwable) throws AccessException
{
    HttpStatus httpStatus = new HttpStatus(statusCode, statusMessage);
    LocalizedMessage templateMessage = new FixedMessage(errorDescription);
    throw new AccessException(AccessExceptionContext.create(httpStatus)
                                                    .exceptionMessage(errorDescription)
                                                    .errorDescription(templateMessage)
                                                    .cause(throwable));
}

BEFORE:

private void throwAccessException() throws AccessException
{
    throw new AccessException(AccessExceptionContext.builder()
                                                    .httpStatusCode(403)
                                                    .httpStatusMessage("Forbidden")
                                                    .build());
}

AFTER:

private void throwAccessException() throws AccessException
{
    throw new AccessException(AccessExceptionContext.create(HttpStatus.FORBIDDEN));
}

Using the AccessException#getExceptionContext method

BEFORE:

AccessExceptionContext context = accessException.getExceptionContext();

AFTER:

This functionality is no longer supported. The information that used to be provided by the AccessExceptionContext is now provided directly by an AccessException.

Using the AccessException#getHttpStatusCode method

BEFORE:

int statusCode = accessException.getHttpStatusCode();

AFTER:

int statusCode = accessException.getErrorStatus().getCode();

Using the AccessException#getHttpStatusMessage method

BEFORE:

String statusMessage = accessException.getHttpStatusMessage();

AFTER:

String statusMessage = accessException.getErrorStatus().getMessage();

Using the AccessException#setHttpStatusCode method

BEFORE:

accessException.setHttpStatusCode(statusCode);

AFTER:

This functionality is no longer supported. The status code associated with an AccessException is fixed once it is constructed.

Using the AccessException#setHttpStatusMessage method

BEFORE:

accessException.setHttpStatusMessage(statusMessage);

AFTER:

This functionality is no longer supported. The status message associated with an AccessException is fixed once it is constructed.

com.pingidentity.pa.sdk.policy.RuleInterceptor

The handleRequest and handleResponse methods on a RuleInterceptor no longer throw an IOException. Instead, they throw an AccessException, which no longer derives from IOException.

Accounting for the RuleInterceptor#handleRequest method signature change

BEFORE:

@Override
public Outcome handleRequest(Exchange exchange) throws IOException
{
    Outcome outcome = applyPolicy(exchange);

    return outcome;
}

AFTER:

@Override
public Outcome handleRequest(Exchange exchange) throws AccessException
{
    Outcome outcome = applyPolicy(exchange);

    return outcome;
}

Account for the RuleInterceptor#handleResponse method signature change

BEFORE:

@Override

public void handleResponse(Exchange exchange) throws IOException

{

    applyPolicyToResponse(exchange.getResponse());

}

AFTER:

@Override
public void handleResponse(Exchange exchange) throws AccessException
{
    applyPolicyToResponse(exchange.getResponse());
}
com.pingidentity.pa.sdk.policy.error.InternalServerErrorCallback

This class has been removed. Use LocalizedInternalServerErrorCallback instead.

com.pingidentity.pa.sdk.services

com.pingidentity.pa.sdk.services.ServiceFactory

This class is now final and cannot be extended.

com.pingidentity.pa.sdk.siteauthenticator

com.pingidentity.pa.sdk.siteauthenticator.SiteAuthenticatorInterceptor

This interface is no longer a RequestInterceptor or ResponseInterceptor, but it still defines the handleRequest and handleResponse methods:

public interface SiteAuthenticatorInterceptor<T extends PluginConfiguration>
        extends DescribesUIConfigurable, ConfigurablePlugin<T>
{
    void handleRequest(Exchange exchange) throws AccessException;
    void handleResponse(Exchange exchange) throws AccessException;
}

Additionally, these methods now only throw an AccessException instead of an IOException or InterruptedException.

Accounting for the SiteAuthenticatorInterceptor#handleRequest method signature change

BEFORE:

@Override
public Outcome handleRequest(Exchange exc)
        throws RuntimeException, IOException, InterruptedException
{
    // Site authenticator implementation //
    
    return Outcome.CONTINUE;
}

AFTER:

@Override
public void handleRequest(Exchange exc) throws AccessException
{
    // Site authenticator implementation //
}

Accounting for the SiteAuthenticatorInterceptor#handleResponse method signature change

BEFORE:

@Override
public void handleResponse(Exchange exc) throws IOException
{
    // Site authenticator response implementation //
}

AFTER:

@Override
public void handleResponse(Exchange exc) throws AccessException
{
    // Site authenticator response implementation //
}

com.pingidentity.pa.sdk.ui

com.pingidentity.pa.sdk.ui.ConfigurationType

The deprecated PRIVATEKEY enum value has been removed. Instead use a ConfigurationType of ConfigurationType#SELECT and specify the PrivateKeyAccessor.class instance to ConfigurationBuilder#dynamicOptions or UIElement#modelAccessor.

com.pingidentity.pa.sdk.user

com.pingidentity.pa.sdk.user.User

This class has been removed from the SDK. Use the Identity interface instead. An instance of Identity can be retrieved from the Exchange, similar to the User interface.

com.pingidentity.pa.sdk.util

com.pingidentity.pa.sdk.util.TemplateRenderer

The semantics of the renderResponse method have changed so it produces a Response and does not have any side-effects on the specified parameters.

Using the TemplateRenderer#renderResponse method:

BEFORE:

private void invokeRenderResponse(TemplateRenderer templateRenderer,
                                  Map<String, String> context,
                                  String templateName,
                                  Exchange exchange,
                                  ResponseBuilder builder)
{
    templateRenderer.renderResponse(context, templateName, exchange, builder);
}

AFTER:

private void invokeRenderResponse(TemplateRenderer templateRenderer,
                                  Map<String, String> context,
                                  String templateName,
                                  Exchange exchange,
                                  ResponseBuilder builder)
{
    Response response = templateRenderer.renderResponse(exchange,
                                                        context,
                                                        templateName,
                                                        builder);
    exchange.setResponse(response);
}

Tags Product > PingAccess > PingAccess 5.2; Product > PingAccess