diff --git a/pom.xml b/pom.xml
index 982d833cc..80bb1c8ca 100644
--- a/pom.xml
+++ b/pom.xml
@@ -51,8 +51,9 @@
2.0.16
4.13.2
7.0.1.Final
- 3.5.7
-
+
+ 3.5.8
+ 4.0.0
@@ -68,6 +69,7 @@
scim-server-examples/scim-server-jersey
scim-server-examples/scim-server-quarkus
scim-server-examples/scim-server-spring-boot
+ scim-server-examples/scim-server-spring-boot-4
support/spring-boot
scim-tools
scim-compliance-tests
@@ -728,11 +730,6 @@
jacoco-maven-plugin
0.8.14
-
- org.springframework.boot
- spring-boot-maven-plugin
- ${version.spring-boot}
-
io.smallrye
jandex-maven-plugin
diff --git a/scim-server-examples/scim-server-spring-boot-4/README.md b/scim-server-examples/scim-server-spring-boot-4/README.md
new file mode 100644
index 000000000..1b7768f14
--- /dev/null
+++ b/scim-server-examples/scim-server-spring-boot-4/README.md
@@ -0,0 +1,19 @@
+Apache Directory SCIMple In Memory Example
+==========================================
+
+This example project demo's how to:
+
+* Add a custom SCIM Extension
+* Manage Users and Groups (in memory)
+
+Use this as a starter point on how to integrate Apache Directory SCIMple into your own project.
+
+Run: `mvn spring-boot:run` and then access one of the endpoints:
+
+```bash
+# httpie
+http :8080/v2/Users
+
+# curl
+curl localhost:8080/v2/Users
+```
diff --git a/scim-server-examples/scim-server-spring-boot-4/pom.xml b/scim-server-examples/scim-server-spring-boot-4/pom.xml
new file mode 100644
index 000000000..9830f4b7f
--- /dev/null
+++ b/scim-server-examples/scim-server-spring-boot-4/pom.xml
@@ -0,0 +1,88 @@
+
+
+ 4.0.0
+
+
+ org.apache.directory.scimple
+ scimple
+ 1.0.0-SNAPSHOT
+ ../..
+
+
+ scim-server-spring-boot4
+ SCIMple - Server - Examples - Spring Boot 4
+
+
+ org.apache.directory.scim.example.springboot4
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ ${version.spring-boot4}
+ pom
+ import
+
+
+
+
+
+
+ org.apache.directory.scimple
+ scim-spring-boot-starter
+
+
+ org.apache.directory.scimple
+ scim-server
+
+
+ ch.qos.logback
+ logback-classic
+ runtime
+
+
+
+ org.junit.jupiter
+ junit-jupiter
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.springframework.boot
+ spring-boot-test
+ test
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+ ${version.spring-boot4}
+
+
+
+
+
diff --git a/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringBootApplication.java b/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringBootApplication.java
new file mode 100644
index 000000000..897cd2a87
--- /dev/null
+++ b/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringBootApplication.java
@@ -0,0 +1,46 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you 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.
+*/
+
+package org.apache.directory.scim.example.spring;
+
+import org.apache.directory.scim.server.configuration.ServerConfiguration;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.Bean;
+
+import static org.apache.directory.scim.spec.schema.ServiceProviderConfiguration.AuthenticationSchema.httpBasic;
+
+@SpringBootApplication
+public class ScimpleSpringBootApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(ScimpleSpringBootApplication.class, args);
+ }
+
+ @Bean
+ ServerConfiguration serverConfiguration() {
+ // Set any unique configuration bits
+ return new ServerConfiguration()
+ .setId("scimple-spring-boot-example")
+ .setDocumentationUri("https://github.com/apache/directory-scimple")
+
+ // set the auth scheme
+ .addAuthenticationSchema(httpBasic());
+ }
+}
diff --git a/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/extensions/LuckyNumberExtension.java b/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/extensions/LuckyNumberExtension.java
new file mode 100644
index 000000000..cdf3c42b2
--- /dev/null
+++ b/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/extensions/LuckyNumberExtension.java
@@ -0,0 +1,92 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you 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.
+*/
+
+package org.apache.directory.scim.example.spring.extensions;
+
+import jakarta.xml.bind.annotation.XmlAccessType;
+import jakarta.xml.bind.annotation.XmlAccessorType;
+import jakarta.xml.bind.annotation.XmlElement;
+import jakarta.xml.bind.annotation.XmlRootElement;
+import org.apache.directory.scim.spec.annotation.ScimAttribute;
+import org.apache.directory.scim.spec.annotation.ScimExtensionType;
+import org.apache.directory.scim.spec.resources.ScimExtension;
+import org.apache.directory.scim.spec.schema.Schema;
+
+/**
+ * Allows a User's lucky number to be passed as part of the User's entry via
+ * the SCIM protocol.
+ *
+ * @author Chris Harm <crh5255@psu.edu>
+ */
+@XmlRootElement( name = "LuckyNumberExtension", namespace = "http://www.psu.edu/schemas/psu-scim" )
+@XmlAccessorType(XmlAccessType.NONE)
+@ScimExtensionType(id = LuckyNumberExtension.SCHEMA_URN, description="Lucky Numbers", name="LuckyNumbers", required=true)
+public class LuckyNumberExtension implements ScimExtension {
+
+ public static final String SCHEMA_URN = "urn:mem:params:scim:schemas:extension:LuckyNumberExtension";
+
+ @ScimAttribute(returned=Schema.Attribute.Returned.DEFAULT, required=true)
+ @XmlElement
+ private long luckyNumber;
+
+ /**
+ * Provides the URN associated with this extension which, as defined by the
+ * SCIM specification is the extension's unique identifier.
+ *
+ * @return The extension's URN.
+ */
+ @Override
+ public String getUrn() {
+ return SCHEMA_URN;
+ }
+
+ public long getLuckyNumber() {
+ return this.luckyNumber;
+ }
+
+ public LuckyNumberExtension setLuckyNumber(long luckyNumber) {
+ this.luckyNumber = luckyNumber;
+ return this;
+ }
+
+ public boolean equals(final Object o) {
+ if (o == this) return true;
+ if (!(o instanceof LuckyNumberExtension)) return false;
+ final LuckyNumberExtension other = (LuckyNumberExtension) o;
+ if (!other.canEqual((Object) this)) return false;
+ if (this.getLuckyNumber() != other.getLuckyNumber()) return false;
+ return true;
+ }
+
+ protected boolean canEqual(final Object other) {
+ return other instanceof LuckyNumberExtension;
+ }
+
+ public int hashCode() {
+ final int PRIME = 59;
+ int result = 1;
+ final long $luckyNumber = this.getLuckyNumber();
+ result = result * PRIME + (int) ($luckyNumber >>> 32 ^ $luckyNumber);
+ return result;
+ }
+
+ public String toString() {
+ return "LuckyNumberExtension(luckyNumber=" + this.getLuckyNumber() + ")";
+ }
+}
diff --git a/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java b/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java
new file mode 100644
index 000000000..4be6b89af
--- /dev/null
+++ b/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java
@@ -0,0 +1,155 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you 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.
+*/
+
+package org.apache.directory.scim.example.spring.service;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.ETag;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.Repository;
+import org.apache.directory.scim.core.schema.SchemaRegistry;
+import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
+import org.apache.directory.scim.spec.exception.ResourceException;
+import org.apache.directory.scim.spec.exception.ResourceNotFoundException;
+import org.apache.directory.scim.spec.filter.Filter;
+import org.apache.directory.scim.spec.filter.FilterExpressions;
+import org.apache.directory.scim.spec.filter.FilterResponse;
+import org.apache.directory.scim.spec.filter.PageRequest;
+import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
+import org.apache.directory.scim.spec.resources.ScimExtension;
+import org.apache.directory.scim.spec.resources.ScimGroup;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+
+@Service
+public class InMemoryGroupService implements Repository {
+
+ private final Map groups = new HashMap<>();
+
+ private final SchemaRegistry schemaRegistry;
+
+ private final PatchHandler patchHandler;
+
+ public InMemoryGroupService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
+ this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
+ }
+
+ @PostConstruct
+ public void init() {
+ ScimGroup group = new ScimGroup();
+ group.setId(UUID.randomUUID().toString());
+ group.setDisplayName("example-group");
+ group.setExternalId("example-group");
+ groups.put(group.getId(), group);
+ }
+
+ @Override
+ public Class getResourceClass() {
+ return ScimGroup.class;
+ }
+
+ @Override
+ public ScimGroup create(ScimGroup resource, Set includedAttributes, Set excludedAttributes) throws UnableToCreateResourceException {
+ String id = UUID.randomUUID().toString();
+
+ // if the external ID is not set, use the displayName instead
+ if (!StringUtils.hasText(resource.getExternalId())) {
+ resource.setExternalId(resource.getDisplayName());
+ }
+
+ // check to make sure the group doesn't already exist
+ boolean existingGroupFound = groups.values().stream()
+ .anyMatch(group -> resource.getExternalId().equals(group.getExternalId()));
+ if (existingGroupFound) {
+ // HTTP leaking into data layer
+ throw new UnableToCreateResourceException(Response.Status.CONFLICT, "Group '" + resource.getExternalId() + "' already exists.");
+ }
+
+ resource.setId(id);
+ groups.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimGroup update(String id, Set etags, ScimGroup resource, Set includedAttributeReferences, Set excludedAttributeReferences) throws ResourceException {
+ if (!groups.containsKey(id)) {
+ throw new ResourceNotFoundException(id);
+ }
+
+ groups.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimGroup patch(String id, Set etags, List patchOperations, Set includedAttributeReferences, Set excludedAttributeReferences) throws ResourceException {
+ if (!groups.containsKey(id)) {
+ throw new ResourceNotFoundException(id);
+ }
+
+ ScimGroup resource = patchHandler.apply(get(id, includedAttributeReferences, excludedAttributeReferences), patchOperations);
+ groups.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimGroup get(String id, Set includedAttributes, Set excludedAttributes) {
+ return groups.get(id);
+ }
+
+ @Override
+ public void delete(String id) throws ResourceNotFoundException {
+ if (groups.remove(id) == null) {
+ throw new ResourceNotFoundException(id);
+ }
+ }
+
+ @Override
+ public FilterResponse find(Filter filter, PageRequest pageRequest, SortRequest sortRequest, Set includedAttributes, Set excludedAttributes) {
+ long count = pageRequest.getCount() != null ? pageRequest.getCount() : groups.size();
+ long startIndex = pageRequest.getStartIndex() != null
+ ? pageRequest.getStartIndex() - 1 // SCIM is 1-based indexed
+ : 0;
+
+ List result = groups.values().stream()
+ .skip(startIndex)
+ .limit(count)
+ .filter(FilterExpressions.inMemory(filter, schemaRegistry.getSchema(ScimGroup.SCHEMA_URI)))
+ .collect(Collectors.toList());
+
+ return new FilterResponse<>(result, pageRequest, result.size());
+ }
+
+ @Override
+ public List> getExtensionList() {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemorySelfResolverImpl.java b/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemorySelfResolverImpl.java
new file mode 100644
index 000000000..4e08395bd
--- /dev/null
+++ b/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemorySelfResolverImpl.java
@@ -0,0 +1,37 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you 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.
+*/
+
+package org.apache.directory.scim.example.spring.service;
+
+import org.apache.directory.scim.core.repository.SelfIdResolver;
+import org.apache.directory.scim.server.exception.UnableToResolveIdResourceException;
+
+import java.security.Principal;
+
+import jakarta.ws.rs.core.Response.Status;
+import org.springframework.stereotype.Service;
+
+@Service
+public class InMemorySelfResolverImpl implements SelfIdResolver {
+
+ @Override
+ public String resolveToInternalId(Principal principal) throws UnableToResolveIdResourceException {
+ throw new UnableToResolveIdResourceException(Status.NOT_IMPLEMENTED, "Caller Principal not available");
+ }
+}
diff --git a/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java b/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
new file mode 100644
index 000000000..ac2ee8e98
--- /dev/null
+++ b/scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java
@@ -0,0 +1,204 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements. See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership. The ASF licenses this file
+* to you 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.
+*/
+
+package org.apache.directory.scim.example.spring.service;
+
+import jakarta.annotation.PostConstruct;
+import jakarta.ws.rs.core.Response;
+import org.apache.directory.scim.core.repository.ETag;
+import org.apache.directory.scim.core.repository.PatchHandler;
+import org.apache.directory.scim.core.repository.Repository;
+import org.apache.directory.scim.core.schema.SchemaRegistry;
+import org.apache.directory.scim.example.spring.extensions.LuckyNumberExtension;
+import org.apache.directory.scim.server.exception.UnableToCreateResourceException;
+import org.apache.directory.scim.spec.exception.ResourceException;
+import org.apache.directory.scim.spec.exception.ResourceNotFoundException;
+import org.apache.directory.scim.spec.extension.EnterpriseExtension;
+import org.apache.directory.scim.spec.filter.Filter;
+import org.apache.directory.scim.spec.filter.FilterExpressions;
+import org.apache.directory.scim.spec.filter.FilterResponse;
+import org.apache.directory.scim.spec.filter.PageRequest;
+import org.apache.directory.scim.spec.filter.SortRequest;
+import org.apache.directory.scim.spec.filter.attribute.AttributeReference;
+import org.apache.directory.scim.spec.patch.PatchOperation;
+import org.apache.directory.scim.spec.resources.Email;
+import org.apache.directory.scim.spec.resources.Name;
+import org.apache.directory.scim.spec.resources.ScimExtension;
+import org.apache.directory.scim.spec.resources.ScimResource;
+import org.apache.directory.scim.spec.resources.ScimUser;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.stream.Collectors;
+
+/**
+ * Creates a singleton (effectively) Provider with a memory-based
+ * persistence layer.
+ *
+ * @author Chris Harm <crh5255@psu.edu>
+ */
+@Service
+public class InMemoryUserService implements Repository {
+
+ static final String DEFAULT_USER_ID = UUID.randomUUID().toString();
+ static final String DEFAULT_USER_EXTERNAL_ID = "e" + DEFAULT_USER_ID;
+ static final String DEFAULT_USER_DISPLAY_NAME = "User " + DEFAULT_USER_ID;
+ static final String DEFAULT_USER_EMAIL_VALUE = "e1@example.com";
+ static final String DEFAULT_USER_EMAIL_TYPE = "work";
+ static final int DEFAULT_USER_LUCKY_NUMBER = 7;
+
+ private final Map users = new ConcurrentHashMap<>();
+
+ private final SchemaRegistry schemaRegistry;
+
+ private final PatchHandler patchHandler;
+
+ public InMemoryUserService(SchemaRegistry schemaRegistry, PatchHandler patchHandler) {
+ this.schemaRegistry = schemaRegistry;
+ this.patchHandler = patchHandler;
+ }
+
+ @PostConstruct
+ public void init() {
+ ScimUser user = new ScimUser();
+ user.setId(DEFAULT_USER_ID);
+ user.setExternalId(DEFAULT_USER_EXTERNAL_ID);
+ user.setUserName(DEFAULT_USER_EXTERNAL_ID);
+ user.setDisplayName(DEFAULT_USER_DISPLAY_NAME);
+ user.setName(new Name()
+ .setGivenName("Tester")
+ .setFamilyName("McTest"));
+ Email email = new Email();
+ email.setDisplay(DEFAULT_USER_EMAIL_VALUE);
+ email.setValue(DEFAULT_USER_EMAIL_VALUE);
+ email.setType(DEFAULT_USER_EMAIL_TYPE);
+ email.setPrimary(true);
+ user.setEmails(List.of(email));
+
+ LuckyNumberExtension luckyNumberExtension = new LuckyNumberExtension();
+ luckyNumberExtension.setLuckyNumber(DEFAULT_USER_LUCKY_NUMBER);
+
+ user.addExtension(luckyNumberExtension);
+
+ EnterpriseExtension enterpriseExtension = new EnterpriseExtension();
+ enterpriseExtension.setEmployeeNumber("12345");
+ EnterpriseExtension.Manager manager = new EnterpriseExtension.Manager();
+ manager.setValue("bulkId:qwerty");
+ enterpriseExtension.setManager(manager);
+ user.addExtension(enterpriseExtension);
+
+ users.put(user.getId(), user);
+ }
+
+ @Override
+ public Class getResourceClass() {
+ return ScimUser.class;
+ }
+
+ /**
+ * @see Repository#create(ScimResource, java.util.Set, java.util.Set)
+ */
+ @Override
+ public ScimUser create(ScimUser resource, Set includedAttributes, Set excludedAttributes) throws UnableToCreateResourceException {
+ String id = UUID.randomUUID().toString();
+
+ // check to make sure the user doesn't already exist
+ boolean existingUserFound = users.values().stream()
+ .anyMatch(user -> user.getUserName().equals(resource.getUserName()));
+ if (existingUserFound) {
+ // HTTP leaking into data layer
+ throw new UnableToCreateResourceException(Response.Status.CONFLICT, "User '" + resource.getUserName() + "' already exists.");
+ }
+
+ resource.setId(id);
+ users.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimUser update(String id, Set etags, ScimUser resource, Set includedAttributeReferences, Set excludedAttributeReferences) throws ResourceException {
+ if (!users.containsKey(id)) {
+ throw new ResourceNotFoundException(id);
+ }
+
+ users.put(id, resource);
+ return resource;
+ }
+
+ @Override
+ public ScimUser patch(String id, Set etags, List patchOperations, Set includedAttributeReferences, Set excludedAttributeReferences) throws ResourceException {
+ if (!users.containsKey(id)) {
+ throw new ResourceNotFoundException(id);
+ }
+
+ ScimUser resource = patchHandler.apply(get(id, includedAttributeReferences, excludedAttributeReferences), patchOperations);
+ users.put(id, resource);
+ return resource;
+ }
+
+ /**
+ * @see Repository#get(java.lang.String, java.util.Set, java.util.Set)
+ */
+ @Override
+ public ScimUser get(String id, Set includedAttributes, Set excludedAttributes) {
+ return users.get(id);
+ }
+
+ /**
+ * @see Repository#delete(java.lang.String)
+ */
+ @Override
+ public void delete(String id) throws ResourceException {
+ if (users.remove(id) == null) {
+ throw new ResourceNotFoundException(id);
+ }
+ }
+
+ /**
+ * @see Repository#find(Filter, PageRequest, SortRequest, java.util.Set, java.util.Set)
+ */
+ @Override
+ public FilterResponse find(Filter filter, PageRequest pageRequest, SortRequest sortRequest, Set includedAttributes, Set excludedAttributes) {
+
+ long count = pageRequest.getCount() != null ? pageRequest.getCount() : users.size();
+ long startIndex = pageRequest.getStartIndex() != null
+ ? pageRequest.getStartIndex() - 1 // SCIM is 1-based indexed
+ : 0;
+
+ List result = users.values().stream()
+ .skip(startIndex)
+ .limit(count)
+ .filter(FilterExpressions.inMemory(filter, schemaRegistry.getSchema(ScimUser.SCHEMA_URI)))
+ .collect(Collectors.toList());
+
+ return new FilterResponse<>(result, pageRequest, result.size());
+ }
+
+ /**
+ * @see Repository#getExtensionList()
+ */
+ @Override
+ public List> getExtensionList() {
+ return List.of(LuckyNumberExtension.class, EnterpriseExtension.class);
+ }
+}
diff --git a/scim-server-examples/scim-server-spring-boot-4/src/main/resources/application.properties b/scim-server-examples/scim-server-spring-boot-4/src/main/resources/application.properties
new file mode 100644
index 000000000..0183be95d
--- /dev/null
+++ b/scim-server-examples/scim-server-spring-boot-4/src/main/resources/application.properties
@@ -0,0 +1,19 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you 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.
+#
+server.servlet.context-path = /v2
diff --git a/scim-server-examples/scim-server-spring-boot/pom.xml b/scim-server-examples/scim-server-spring-boot/pom.xml
index 17e10405e..6248ab749 100644
--- a/scim-server-examples/scim-server-spring-boot/pom.xml
+++ b/scim-server-examples/scim-server-spring-boot/pom.xml
@@ -25,11 +25,10 @@
scim-server-spring-boot
- SCIMple - Server - Examples - Spring Boot
+ SCIMple - Server - Examples - Spring Boot 3
org.apache.directory.scim.example.springboot
- 17
@@ -37,7 +36,7 @@
org.springframework.boot
spring-boot-dependencies
- ${version.spring-boot}
+ ${version.spring-boot3}
pom
import
@@ -81,6 +80,7 @@
org.springframework.boot
spring-boot-maven-plugin
+ ${version.spring-boot3}
diff --git a/support/spring-boot/pom.xml b/support/spring-boot/pom.xml
index 8c4cb5273..930f7380a 100644
--- a/support/spring-boot/pom.xml
+++ b/support/spring-boot/pom.xml
@@ -38,7 +38,7 @@
org.springframework.boot
spring-boot-dependencies
- ${version.spring-boot}
+ ${version.spring-boot3}
pom
import
diff --git a/support/spring-boot/src/main/java/org/apache/directory/scim/spring/ScimpleSpringConfiguration.java b/support/spring-boot/src/main/java/org/apache/directory/scim/spring/ScimpleSpringConfiguration.java
index a976a3304..4927bc59c 100644
--- a/support/spring-boot/src/main/java/org/apache/directory/scim/spring/ScimpleSpringConfiguration.java
+++ b/support/spring-boot/src/main/java/org/apache/directory/scim/spring/ScimpleSpringConfiguration.java
@@ -40,7 +40,6 @@
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
-import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -54,7 +53,9 @@
* Autoconfigures default beans needed for Apache SCIMple.
*/
@Configuration
-@AutoConfigureBefore(JerseyAutoConfiguration.class)
+@AutoConfigureBefore(name = {
+ "org.springframework.boot.jersey.autoconfigure.JerseyAutoConfiguration", // Spring Boot 4
+ "org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration"}) // Spring Boot 3
public class ScimpleSpringConfiguration {
@Bean