From 8ef11f1146a8fdc81adf067905a01ce1f41a093a Mon Sep 17 00:00:00 2001 From: Brian Demers Date: Wed, 17 Dec 2025 17:54:26 -0500 Subject: [PATCH] Add support for Spring Boot 4 The Spring support module still compiles with 3, but there are example projects for 3 and 4, both run the integration test suite. Spring Boot 3 support will be removed when it is end of life. --- pom.xml | 11 +- .../scim-server-spring-boot-4/README.md | 19 ++ .../scim-server-spring-boot-4/pom.xml | 88 ++++++++ .../spring/ScimpleSpringBootApplication.java | 46 ++++ .../extensions/LuckyNumberExtension.java | 92 ++++++++ .../spring/service/InMemoryGroupService.java | 155 +++++++++++++ .../service/InMemorySelfResolverImpl.java | 37 ++++ .../spring/service/InMemoryUserService.java | 204 ++++++++++++++++++ .../src/main/resources/application.properties | 19 ++ .../scim-server-spring-boot/pom.xml | 6 +- support/spring-boot/pom.xml | 2 +- .../spring/ScimpleSpringConfiguration.java | 5 +- 12 files changed, 671 insertions(+), 13 deletions(-) create mode 100644 scim-server-examples/scim-server-spring-boot-4/README.md create mode 100644 scim-server-examples/scim-server-spring-boot-4/pom.xml create mode 100644 scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/ScimpleSpringBootApplication.java create mode 100644 scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/extensions/LuckyNumberExtension.java create mode 100644 scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryGroupService.java create mode 100644 scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemorySelfResolverImpl.java create mode 100644 scim-server-examples/scim-server-spring-boot-4/src/main/java/org/apache/directory/scim/example/spring/service/InMemoryUserService.java create mode 100644 scim-server-examples/scim-server-spring-boot-4/src/main/resources/application.properties 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