Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,18 @@
All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](https://semver.org/).

## v5.0.1 - TBD
## v5.1.0 - TBD
Updated Jackson to 2.20.1.

Updated `ErrorResponse.java` to print attributes in an order that is more consistent with the
example JSON objects presented in RFC 7644. Now, `status` is the last attribute printed.

Added new `ContentTooLargeException` and `RateLimitException` classes corresponding to the HTTP 413
and HTTP 429 status codes.

Added new `status()` and `statusInt()` method to all exception types. For example, to obtain a
string of `"400"` corresponding to `400 BAD REQUEST`, use `BadRequestException.status()`.

## v5.0.0 - 2025-Dec-15
For consistency with other open source Ping Identity software, the UnboundID SCIM 2 SDK for Java is
now available under the terms of the Apache License (version 2.0). For legacy compatibility, the
Expand Down
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>com.unboundid.product.scim2</groupId>
<artifactId>scim2-parent</artifactId>
<version>5.0.1-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>UnboundID SCIM2 SDK Parent</name>
<description>
Expand Down
2 changes: 1 addition & 1 deletion scim2-assembly/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<parent>
<groupId>com.unboundid.product.scim2</groupId>
<artifactId>scim2-parent</artifactId>
<version>5.0.1-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>scim2-assembly</artifactId>
Expand Down
2 changes: 1 addition & 1 deletion scim2-sdk-client/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<parent>
<groupId>com.unboundid.product.scim2</groupId>
<artifactId>scim2-parent</artifactId>
<version>5.0.1-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>scim2-sdk-client</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
package com.unboundid.scim2.client;

import com.fasterxml.jackson.jakarta.rs.json.JacksonJsonProvider;
import com.unboundid.scim2.client.requests.BulkRequestBuilder;
import com.unboundid.scim2.client.requests.CreateRequestBuilder;
import com.unboundid.scim2.client.requests.DeleteRequestBuilder;
import com.unboundid.scim2.client.requests.ModifyRequestBuilder;
Expand All @@ -56,6 +57,7 @@
import jakarta.ws.rs.core.MediaType;
import java.net.URI;

import static com.unboundid.scim2.common.utils.ApiConstants.BULK_ENDPOINT;
import static com.unboundid.scim2.common.utils.ApiConstants.MEDIA_TYPE_SCIM;
import static com.unboundid.scim2.common.utils.ApiConstants.ME_ENDPOINT;
import static com.unboundid.scim2.common.utils.ApiConstants.RESOURCE_TYPES_ENDPOINT;
Expand Down Expand Up @@ -575,6 +577,55 @@ public <T extends ScimResource> DeleteRequestBuilder deleteRequest(
return deleteRequest(checkAndGetLocation(resource));
}

/**
* Build a request that will perform multiple write operations in a single API
* call. See {@link com.unboundid.scim2.common.bulk.BulkRequest BulkRequest}
* for more information.
*
* @return The request builder that may be used to specify additional
* operations and to invoke the request.
*/
@NotNull
public BulkRequestBuilder bulkRequest()
{
return new BulkRequestBuilder(baseTarget.path(BULK_ENDPOINT));
}

/**
* Build a request that will perform multiple write operations in a single API
* call to the specified endpoint. In general, {@link #bulkRequest()} should
* be used to target the {@code /Bulk} endpoint defined in the specification.
* See {@link com.unboundid.scim2.common.bulk.BulkRequest BulkRequest} for
* more information.
*
* @param endpoint The resource endpoint, e.g., {@code /Bulk}.
* @return The request builder that may be used to specify additional
* operations and to invoke the request.
*/
@NotNull
public BulkRequestBuilder bulkRequest(@NotNull final String endpoint)
{
return new BulkRequestBuilder(baseTarget.path(endpoint));
}

/**
* Build a request that will perform multiple write operations in a single API
* call to the specified URI. In general, {@link #bulkRequest()} should be
* used to target the {@code /Bulk} endpoint defined in the specification. See
* {@link com.unboundid.scim2.common.bulk.BulkRequest BulkRequest} for more
* information.
*
* @param uri The URI referencing a SCIM service's bulk request endpoint,
* e.g., {@code https://example.com/v2/Bulk}.
* @return The request builder that may be used to specify additional
* operations and to invoke the request.
*/
@NotNull
public BulkRequestBuilder bulkRequest(@NotNull final URI uri)
{
return new BulkRequestBuilder(resolveWebTarget(uri));
}

/**
* Resolve a URL (relative or absolute) to a web target.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* Copyright 2026 Ping Identity Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2026 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>.
*/

package com.unboundid.scim2.client.requests;

import com.unboundid.scim2.common.annotations.NotNull;
import com.unboundid.scim2.common.annotations.Nullable;
import com.unboundid.scim2.common.exceptions.ScimException;
import com.unboundid.scim2.common.bulk.BulkOperation;
import com.unboundid.scim2.common.bulk.BulkRequest;
import com.unboundid.scim2.common.bulk.BulkResponse;
import jakarta.ws.rs.HttpMethod;
import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.client.WebTarget;
import jakarta.ws.rs.core.Response;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import static jakarta.ws.rs.core.Response.Status.Family.SUCCESSFUL;


/**
* This class provides a builder for SCIM 2 bulk requests. Bulk requests provide
* a way for clients to send multiple write operations in a single API call.
* <br><br>
*
* For more information, see the documentation in {@link BulkRequest}.
*/
public class BulkRequestBuilder
extends ResourceReturningRequestBuilder<BulkRequestBuilder>
{
@NotNull
private final List<BulkOperation> operations = new ArrayList<>();

/**
* Create a new bulk request builder that will be used to apply a list of
* write operations to the given web target.
*
* @param target The WebTarget that will receive the bulk request.
*/
public BulkRequestBuilder(@NotNull final WebTarget target)
{
super(target);
}

/**
* Append a list of bulk operations to this bulk request. Any {@code null}
* values will be ignored.
*
* @param ops The list of bulk operations to add.
*
* @return This bulk request builder.
*/
@NotNull
public BulkRequestBuilder append(@Nullable final List<BulkOperation> ops)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The other builders have addOperation. Would addOperations be a good name for this method?

Copy link
Copy Markdown
Collaborator Author

@kqarryzada kqarryzada Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I considered this, but the term "operation" felt like it was getting overloaded between "patch operation" and "bulk operation". This will become more noticeable in Phase 2, where there is a bulk operation type for patch requests, so the nomenclature has the potential to become confusing. So I decided to name this as "append" just to avoid confusion. What do you think?

{
List<BulkOperation> operationList = (ops == null) ? List.of() : ops;

// Null fields are handled when the bulk request is constructed.
operations.addAll(operationList);
return this;
}

/**
* Append one or more bulk operations to this bulk request. Any {@code null}
* values will be ignored.
*
* @param operations The bulk operation(s) to add.
*
* @return This bulk request builder.
*/
@NotNull
public BulkRequestBuilder append(@Nullable final BulkOperation... operations)
{
if (operations != null)
{
append(Arrays.asList(operations));
}

return this;
}

/**
* Invoke the SCIM bulk request and return the response.
*
* @return The bulk response representing the summary of the write requests
* that were attempted and whether they were successful.
* @throws ScimException If the SCIM service responded with an error.
*/
@NotNull
public BulkResponse invoke() throws ScimException
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The layout of this class largely follows the other builders. However, the invoke() method is somewhat unique because any SCIM resources will be included within the BulkResponse, so the returned object is never typed. This is why we'll need to have the mapper class in Phase 2.

{
return invoke(BulkResponse.class);
}

/**
* Invoke the SCIM bulk request.
*
* @param <C> The Java type to return. This should be a {@link BulkResponse}.
* @param cls The Java type to return. This should be a {@link BulkResponse}.
*
* @return The bulk response.
* @throws ScimException If the SCIM service responded with an error, such
* as if the JSON body was too large.
*/
@NotNull
protected <C> C invoke(@NotNull final Class<C> cls)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you wanted this protected? Many of the other builds just have public.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I set this as protected because there's virtually no reason I can think of that a caller would want anything other than a BulkResponse object returned. However, if they really need it, they could extend this class and achieve that result this way. Making it protected allows for this, even though I think it's unlikely.

throws ScimException
{
BulkRequest request = new BulkRequest(operations);
var entity = Entity.entity(generify(request), getContentType());

try (Response response = buildRequest().method(HttpMethod.POST, entity))
{
if (response.getStatusInfo().getFamily() == SUCCESSFUL)
{
return response.readEntity(cls);
}
else
{
throw toScimException(response);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

package com.unboundid.scim2.client;

import com.unboundid.scim2.client.requests.BulkRequestBuilder;
import com.unboundid.scim2.client.requests.CreateRequestBuilder;
import com.unboundid.scim2.client.requests.DeleteRequestBuilder;
import com.unboundid.scim2.client.requests.ReplaceRequestBuilder;
Expand Down Expand Up @@ -112,5 +113,19 @@ public CustomSearch(WebTarget target)
var searchInstance = new CustomSearch(target);
searchInstance.otherField = "present";
assertThat(searchInstance.otherField).isEqualTo("present");

// Test BulkRequestBuilder.
class CustomBulk extends BulkRequestBuilder
{
public String otherField;

public CustomBulk(WebTarget target)
{
super(target);
}
}
var bulkInstance = new CustomBulk(target);
bulkInstance.otherField = "present";
assertThat(bulkInstance.otherField).isEqualTo("present");
}
}
2 changes: 1 addition & 1 deletion scim2-sdk-common/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<parent>
<groupId>com.unboundid.product.scim2</groupId>
<artifactId>scim2-parent</artifactId>
<version>5.0.1-SNAPSHOT</version>
<version>5.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>scim2-sdk-common</artifactId>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright 2026 Ping Identity Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
* Copyright 2026 Ping Identity Corporation
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License (GPLv2 only)
* or the terms of the GNU Lesser General Public License (LGPLv2.1 only)
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses>.
*/

package com.unboundid.scim2.common.bulk;

import com.unboundid.scim2.common.annotations.NotNull;


/**
* An enum representing possible bulk operation types.
*/
public enum BulkOpType
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this phase 2? It does not seem to be used, at least internally. If you just want the constants there is Jakarta, but I understand that enums are helpful, and indicate a specific set of allowed value. In any case, if we need this it seems it could be simplified, pretty much:

public enum BulkOpType {
  POST, PUT, PATCH, DELETE
}

But I'm probably missing something.

Copy link
Copy Markdown
Collaborator Author

@kqarryzada kqarryzada Mar 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great question, yes, this is needed for Phase 2.

Also, it's worth noting that the majority of these changes are placed in scim2-sdk-common since entities like bulk operations are model objects. The only dependency that common has is Jackson, so Jakarta utilities are not available to us in the common module anyway.

{
/**
* The "POST" bulk operation type used to create a resource.
*/
POST("POST"),

/**
* The "PUT" bulk operation type used to overwrite a resource.
*/
PUT("PUT"),

/**
* The "PATCH" bulk operation type used to update part of a resource.
*/
PATCH("PATCH"),

/**
* The "DELETE" bulk operation type used to delete a resource.
*/
DELETE("DELETE"),

// GET is not available, since bulk operations are for writes.
;

@NotNull
private final String stringValue;

BulkOpType(@NotNull final String stringValue)
{
this.stringValue = stringValue;
}

@Override
@NotNull
public String toString()
{
return stringValue;
}
}
Loading
Loading