How to add a NameId value to a AttributeValue using OpenSAML2 - opensaml

Using OpenSAML2 how does one create the following XML:
<saml:Attribute Name="urn:mace:dir:attribute-def:eduPersonTargetedID"
NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri">
<saml:AttributeValue>
<saml:NameID Format="urn:oasis:names:tc:SAML:2.0:nameid-format:persistent">c693b1c47a0da7de6518bc30a1bb8d2e44b56980</saml:NameID>
</saml:AttributeValue>
</saml:Attribute>

Extending OpenSAML fixes the issue as it doesn't seem to support NameID values within Attribute Value elements.
The following files are required to implement the AttributeValue.
Builder
package com.blah;
import org.opensaml.common.impl.AbstractSAMLObjectBuilder;
import org.opensaml.common.xml.SAMLConstants;
import org.opensaml.saml2.core.AttributeValue;
public class AttributeValueBuilder extends AbstractSAMLObjectBuilder<AttributeValue>{
public AttributeValueBuilder() {
}
#Override
public AttributeValue buildObject() {
return buildObject(SAMLConstants.SAML20_NS, AttributeValue.DEFAULT_ELEMENT_LOCAL_NAME, SAMLConstants.SAML20_PREFIX);
}
#Override
public AttributeValue buildObject(String namespaceURI, String localName, String namespacePrefix) {
return new AttributeValueImpl(namespaceURI, localName, namespacePrefix);
}
}
Implementation
package com.blah;
import java.util.ArrayList;
import java.util.List;
import org.opensaml.common.impl.AbstractSAMLObject;
import org.opensaml.xml.XMLObject;
public class AttributeValueImpl extends AbstractSAMLObject implements org.opensaml.saml2.core.AttributeValue{
protected AttributeValueImpl(String namespaceURI, String elementLocalName,
String namespacePrefix) {
super(namespaceURI, elementLocalName, namespacePrefix);
}
private List<XMLObject> children = new ArrayList<XMLObject>();
#Override
public List<XMLObject> getOrderedChildren() {
return children;
}
}
Marshaller
package com.blah;
import org.opensaml.common.impl.AbstractSAMLObjectMarshaller;
public class AttributeValueMarshaller extends AbstractSAMLObjectMarshaller {
}
Unmarshaller
package com.blah;
import org.opensaml.common.impl.AbstractSAMLObjectUnmarshaller;
import org.opensaml.xml.XMLObject;
import org.opensaml.xml.io.UnmarshallingException;
public class AttributeValueUnmarshaller extends AbstractSAMLObjectUnmarshaller {
#Override
protected void processChildElement(XMLObject parentSAMLObject, XMLObject childSAMLObject)
throws UnmarshallingException {
AttributeValueImpl attributeValue = (AttributeValueImpl) parentSAMLObject;
attributeValue.getOrderedChildren().add(childSAMLObject);
}
}
Once these files are included they need to be added to the OpenSAML bootstrapping configuration file saml2-assertion-config.xml (I copied it from the OpenSAML jar and placed it into the root of the Java src):
<!-- AttributeValue -->
<ObjectProvider qualifiedName="saml2:AttributeValue">
<BuilderClass className="com.blah.AttributeValueBuilder" />
<MarshallingClass className="com.blah.AttributeValueMarshaller" />
<UnmarshallingClass className="com.blah.AttributeValueUnmarshaller" />
</ObjectProvider>
<ObjectProvider qualifiedName="saml2:AttributeValueType">
<BuilderClass className="com.blah.AttributeValueBuilder" />
<MarshallingClass className="com.blah.AttributeValueMarshaller" />
<UnmarshallingClass className="com.blah.AttributeValueUnmarshaller" />
</ObjectProvider>
It is now possible to add any element to the Attribute Value body.
private static XMLObject createAttributeValueNameId(String value) throws ConfigurationException {
XMLObjectBuilder<AttributeValueImpl> attrBuilder = getSamlBuilder().getBuilder(AttributeValue.DEFAULT_ELEMENT_NAME);
AttributeValueImpl attributeValue = attrBuilder.buildObject(AttributeValue.DEFAULT_ELEMENT_NAME);
XMLObjectBuilder<AttributeValue> builder = getSamlBuilder().getBuilder(NameID.DEFAULT_ELEMENT_NAME);
NameID nameId = (NameID) builder.buildObject(NameID.DEFAULT_ELEMENT_NAME);
nameId.setFormat(NameID.UNSPECIFIED);
nameId.setValue(value);
attributeValue.getOrderedChildren().add(nameId);
return attributeValue;
}

Related

How to get osgi configuration values to a servlet

This is my OSGI configuration file which is having three names. I want to read these values in a servlet and sort them Alphabetically and send that response to a ajax to display in a custom component AEM.
package com.demo.training.core.services.impl;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.AttributeType;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
import com.demo.training.core.services.MyProjectServ;
#Component(service=MyProjectServ.class,immediate = true)
#Designate(ocd= MyProject.ServiceConfig.class)
public class MyProject implements MyProjectServ {
#ObjectClassDefinition(name="My-Project OSGI",
description="Demo OSGI configuration")
public #interface ServiceConfig {
#AttributeDefinition(
name="Name1",
description="Add First name",
type = AttributeType.STRING
)
public String Name1() default "Abhinay";
#AttributeDefinition(
name="Name2",
description="Add second name ",
type = AttributeType.STRING
)
public String Name2() default "Pavan";
#AttributeDefinition(
name="Name3",
description="Add third name ",
type = AttributeType.STRING )
public String Name3() default "Ram";
}
private String Name1;
private String Name2;
private String Name3;
#Activate
protected void activate(ServiceConfig myconfig) {
Name1=myconfig.Name1();
Name2=myconfig.Name2();
Name3=myconfig.Name3();
}
#Override
public String getNAME1() {
return Name1; }
#Override
public String getNAME2() {
return Name2; }
#Override
public String getNAME3() {
return Name3;
} }
'''This is my Servlet code , I have wrote multiple resp.getwriter() to see upto which line it is working. It is working upto response named a1(i.e below dictionary command). Could anyone please help to get values from osgi configuration to this servlet ?
package com.demo.training.core.servlets;
import java.io.IOException;
import java.util.Arrays;
import java.util.Dictionary;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.SlingHttpServletResponse;
import org.apache.sling.api.servlets.HttpConstants;
import org.apache.sling.api.servlets.SlingAllMethodsServlet;
import org.osgi.framework.Constants;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.Reference;
#Component(service=Servlet.class,
property={
Constants.SERVICE_DESCRIPTION + "=Practice Servlet",
"sling.servlet.methods=" + HttpConstants.METHOD_GET,
"sling.servlet.methods=" + HttpConstants.METHOD_POST,
"sling.servlet.paths=/bin/myproject",
"sling.servlet.extensions=" + "txt"
})
public class MyProjectServlet extends SlingAllMethodsServlet {/**
*
*/
private static final long serialVersionUID = 1L;
#Reference
private ConfigurationAdmin MYPROJECT_CONFIG;
private static final String MY_PROJECT="com.demo.training.core.services.impl.MyProject";
#Override
protected void doGet(final SlingHttpServletRequest req,
final SlingHttpServletResponse resp) throws ServletException, IOException {
Configuration My_Servlet=MYPROJECT_CONFIG.getConfiguration(MY_PROJECT);
Dictionary<String,Object> property =My_Servlet.getProperties();
resp.getWriter().write("a1");
String first=property.get("Name1").toString();
String second=property.get("Name2").toString();
String third=property.get("Name3").toString();
resp.getWriter().write("a2");
resp.getWriter().write(first);
resp.getWriter().write("a3");
String[] myArray = new String[]{first,second,third};
Arrays.sort(myArray);
String js=myArray.toString();
resp.getWriter().write(js);
}
}
You try to use the #reference annotation for your service. If this object null you can use the ResourceResolverFactoy. This object does always exists, else you have your instance has a serious problem:
Map<String, Object> serviceParameter = new HashMap<>();
serviceParameter.put(ResourceResolverFactory.SUBSERVICE, Put the name name of your service here);
return resolverFactory.getServiceResourceResolver(serviceParameter);
In Servlet use annotation #reference to inject the ResourceResolverFactoy:
#Reference
private ResourceResolverFactory ...;
By the way, have an eye to java code convetions. Method names starts always with smal letters even in service configs.

Create a custom RestControllerAnotation to execute a requestMapping

Good afternoon,
I have a restController and I want to create an annotation that allows or not to execute a method based on an a custom header value.
If custom header tag equals something then the method must execute, if the custom header dont match, the method musth not execute
I have followed several articles but I have not been able.
I attached the code I created:
Annotation Code:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface ApiVersion {
int[] value();
}
ApiVersionRequestMappingHandlerMapping
public class ApiVersionRequestMappingHandlerMapping extends RequestMappingHandlerMapping {
private final String prefix;
public ApiVersionRequestMappingHandlerMapping(String prefix) {
this.prefix = prefix;
}
#Override
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
RequestMappingInfo info = super.getMappingForMethod(method, handlerType);
if(info == null) return null;
ApiVersion methodAnnotation = AnnotationUtils.findAnnotation(method, ApiVersion.class);
if(methodAnnotation != null) {
RequestCondition<?> methodCondition = getCustomMethodCondition(method);
// Concatenate our ApiVersion with the usual request mapping
info = createApiVersionInfo(methodAnnotation, methodCondition).combine(info);
} else {
ApiVersion typeAnnotation = AnnotationUtils.findAnnotation(handlerType, ApiVersion.class);
if(typeAnnotation != null) {
RequestCondition<?> typeCondition = getCustomTypeCondition(handlerType);
// Concatenate our ApiVersion with the usual request mapping
info = createApiVersionInfo(typeAnnotation, typeCondition).combine(info);
}
}
return info;
}
private RequestMappingInfo createApiVersionInfo(ApiVersion annotation, RequestCondition<?> customCondition) {
int[] values = annotation.value();
String[] patterns = new String[values.length];
for(int i=0; i<values.length; i++) {
// Build the URL prefix
patterns[i] = prefix+values[i];
}
return new RequestMappingInfo(
new PatternsRequestCondition(patterns, getUrlPathHelper(), getPathMatcher(), useSuffixPatternMatch(), useTrailingSlashMatch(), getFileExtensions()),
new RequestMethodsRequestCondition(),
new ParamsRequestCondition(),
new HeadersRequestCondition(),
new ConsumesRequestCondition(),
new ProducesRequestCondition(),
customCondition);
}
}
Rest Controller
#RestController
#RequiredArgsConstructor
#RequestMapping("/api/example")
public class ExampleController {
private final UserService userService;
#ApiVersion (1)
#GetMapping("/myMethod")
public String myMethod(#AuthenticationPrincipal UserAuthenticatedDetails userAuthenticated) {
return userAuthenticated.getUsername();
}
}
ApiConfig
package xx.package.sample;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.transaction.annotation.EnableTransactionManagement;
#ComponentScan("xx.package")
#Configuration
#EnableTransactionManagement
#EntityScan("xx.package.domain.entity")
#EnableJpaRepositories("xx.package.domain.repository")
#EnableAutoConfiguration
public class ApiConfig {
}
I know I'm missing something but I can't see what.
Regards, and thank you very much!
You could use #GetMapping(path = "/myMethod", headers = "My-Header=myValue").
a sequence of "My-Header=myValue" style expressions, with a request
only mapped if each such header is found to have the given value
see https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/bind/annotation/GetMapping.html#headers--

returning MyBatis resultMap as map

I have a mybatis resultMap refer to a POJO class as like below.
<resultMap id="FolderResultMap" type="Folder">
<result column="recordcount" property="recordCount" />
<result column="contenttype" property="folderContentType" />
<result column="folderid" property="folderId" />
<result column="folderdesc" property="folderDescription" />
<result column="foldername" property="folderName" />
<result column="foldertype" property="folderType" />
</resultMap>
<select id="findReportFolders" resultMap="FolderResultMap">
some query
</select>
And in my Mapper interface
List<Folder> findReportFolders (#Param("name") long id,
#Param("id2") long busid);
Because of this i am getting JSON response as list of objects where i need map of list of objects as below mentioned.
{
"folders": [
{
"recordCount": 7,
"folderContentType": "Reports",
"folderId": 139491,
"folderDescription": null,
"folderName": "AA_TestPrivateFolder1234",
"folderType": "CUSTOM",
"refreshable": true
},
{
"recordCount": 35,
"folderContentType": "Reports",
"folderId": 140109,
"folderDescription": "Default Folder for New Reports",
"folderName": "label.privateReportInboxOverrideName",
"folderType": "INBOX",
"refreshable": true
}]
}
This what i am getting now. I would like to get the response as above.
[{"folderId":359056,"folderName":"BE Shared Report Inbox","folderDescription":"BE Shared Report Inbox","folderType":"INBOX","folderContentType":"SharedReports","recordCount":0,"refreshable":true},{"folderId":363984,"folderName":"Default Inbox Folder","folderDescription":"Default Folder for New Reports","folderType":"INBOX","folderContentType":"Reports","recordCount":0,"refreshable":true}]
Any Idea how can i do this?
Nothing in your example JSON requires a Map of anything.
Instead,
create an object hierarchy that represents the JSON structure that you need and use Jackson (or some other JSON library) to generate the JSON.
Here is some example code that uses Jackson:
package blam.won;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Blam
{
#JsonProperty
private String folderContentType;
#JsonProperty
private int recordCount;
public void setFolderContentType(
final String newValue)
{
folderContentType = newValue;
}
public void setRecordCount(
final int newValue)
{
recordCount = newValue;
}
}
package blam.won;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonProperty;
public class Kapow
{
#JsonProperty("folders")
private List<Blam> blamList;
public void setBlamList(
final List<Blam> newValue)
{
blamList = newValue;
}
}
package blam.won;
import java.util.LinkedList;
import java.util.List;
import org.junit.Before;
import org.junit.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public class TestWon
{
private ObjectMapper objectMapper;
#Before
public void preTestSetup()
{
objectMapper = new ObjectMapper();
}
#Test
public void testKapow()
throws JsonProcessingException
{
final Blam blam1 = new Blam();
final Blam blam2 = new Blam();
final List<Blam> blamList = new LinkedList<>();
final String jsonString;
final Kapow kapow = new Kapow();
blam1.setFolderContentType("Reports");
blam1.setRecordCount(7);
blamList.add(blam1);
blam2.setFolderContentType("Reports");
blam2.setRecordCount(35);
blamList.add(blam2);
kapow.setBlamList(blamList);
jsonString = objectMapper.writeValueAsString(kapow);
System.out.println(jsonString);
}
}

serializing annotations as well as fields to JSON

I have a spring boot app, and I want to send DTO validation constraints as well as field value to the client.
Having DTO
class PetDTO {
#Length(min=5, max=15)
String name;
}
where name happens to be 'Leviathan', should result in this JSON being sent to client:
{
name: 'Leviathan'
name_constraint: { type: 'length', min:5, max: 15},
}
Reasoning is to have single source of truth for validations. Can this be done with reasonable amount of work?
To extend Frederik's answer I'll show a little sample code that convers an object to map and serializes it.
So here is the User pojo:
import org.hibernate.validator.constraints.Length;
public class User {
private String name;
public User(String name) {
this.name = name;
}
#Length(min = 5, max = 15)
public String getName() {
return name;
}
}
Then the actual serializer:
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import org.springframework.util.ReflectionUtils;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.*;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toMap;
public class UserSerializer extends StdSerializer<User> {
public UserSerializer(){
this(User.class);
}
private UserSerializer(Class t) {
super(t);
}
#Override
public void serialize(User bean, JsonGenerator gen, SerializerProvider provider) throws IOException {
Map<String, Object> properties = beanProperties(bean);
gen.writeStartObject();
for (Map.Entry<String, Object> entry : properties.entrySet()) {
gen.writeObjectField(entry.getKey(), entry.getValue());
}
gen.writeEndObject();
}
private static Map<String, Object> beanProperties(Object bean) {
try {
return Arrays.stream(Introspector.getBeanInfo(bean.getClass(), Object.class).getPropertyDescriptors())
.filter(descriptor -> Objects.nonNull(descriptor.getReadMethod()))
.flatMap(descriptor -> {
String name = descriptor.getName();
Method getter = descriptor.getReadMethod();
Object value = ReflectionUtils.invokeMethod(getter, bean);
Property originalProperty = new Property(name, value);
Stream<Property> constraintProperties = Stream.of(getter.getAnnotations())
.map(anno -> new Property(name + "_constraint", annotationProperties(anno)));
return Stream.concat(Stream.of(originalProperty), constraintProperties);
})
.collect(toMap(Property::getName, Property::getValue));
} catch (Exception e) {
return Collections.emptyMap();
}
}
// Methods from Annotation.class
private static List<String> EXCLUDED_ANNO_NAMES = Arrays.asList("toString", "equals", "hashCode", "annotationType");
private static Map<String, Object> annotationProperties(Annotation anno) {
try {
Stream<Property> annoProps = Arrays.stream(Introspector.getBeanInfo(anno.getClass(), Proxy.class).getMethodDescriptors())
.filter(descriptor -> !EXCLUDED_ANNO_NAMES.contains(descriptor.getName()))
.map(descriptor -> {
String name = descriptor.getName();
Method method = descriptor.getMethod();
Object value = ReflectionUtils.invokeMethod(method, anno);
return new Property(name, value);
});
Stream<Property> type = Stream.of(new Property("type", anno.annotationType().getName()));
return Stream.concat(type, annoProps).collect(toMap(Property::getName, Property::getValue));
} catch (IntrospectionException e) {
return Collections.emptyMap();
}
}
private static class Property {
private String name;
private Object value;
public Property(String name, Object value) {
this.name = name;
this.value = value;
}
public String getName() {
return name;
}
public Object getValue() {
return value;
}
}
}
And finally we need to register this serializer to be used by Jackson:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#SpringBootApplication(scanBasePackages = "sample.spring.serialization")
public class SerializationApp {
#Bean
public Jackson2ObjectMapperBuilder mapperBuilder(){
Jackson2ObjectMapperBuilder jackson2ObjectMapperBuilder = new Jackson2ObjectMapperBuilder();
jackson2ObjectMapperBuilder.serializers(new UserSerializer());
return jackson2ObjectMapperBuilder;
}
public static void main(String[] args) {
SpringApplication.run(SerializationApp.class, args);
}
}
#RestController
class SerializationController {
#GetMapping("/user")
public User user() {
return new User("sample");
}
}
The Json that will be emitted:
{
"name_constraint":{
"min":5,
"max":15,
"payload":[],
"groups":[],
"message":"{org.hibernate.validator.constraints.Length.message}",
"type":"org.hibernate.validator.constraints.Length"
},
"name":"sample"
}
Hope this helps. Good luck.
You can always use a custom Jackson Serializer for this. Plenty of docs to do this can be found on the internet, might look something like this:
public void serialize(PetDTO value, JsonGenerator jgen, ...) {
jgen.writeStartObject();
jgen.writeNumberField("name", value.name);
jgen.writeObjectField("name_consteaint", getConstraintValue(value));
}
public ConstaintDTO getConstraintValue(PetDTO value) {
// Use reflection to check if the name field on the PetDTO is annotated
// and extract the min, max and type values from the annotation
return new ConstaintDTO().withMaxValue(...).withMinValue(...).ofType(...);
}
You may want to create a base-DTO class for which the converter kicks in so you don't have to create a custom converter for all your domain objects that need to expose the constraints.
By combining reflection and smart use of writing fields, you can get close. Downside is you can't take advantage of the #JsonXXX annotations on your domain objects, since you're writing the JSON yourself.
More ideal solution whould be to have Jackson convert, but have some kind of post-conversion-call to add additional XX_condtion properties to the object. Maybe start by overriding the default object-serializer (if possible)?

Spring Data REST #Idclass not recognized

I have an entity named EmployeeDepartment as below
#IdClass(EmployeeDepartmentPK.class) //EmployeeDepartmentPK is a serializeable object
#Entity
EmployeeDepartment{
#Id
private String employeeID;
#Id
private String departmentCode;
---- Getters, Setters and other props/columns
}
and I have a Spring Data Repository defined as as below
#RepositoryRestResource(....)
public interface IEmployeeDepartmentRepository extends PagingAndSortingRepository<EmployeeDepartment, EmployeeDepartmentPK> {
}
Further, I have a converter registered to convert from String to EmployeeDepartmentPK.
Now, for an entity, qualified by ID employeeID="abc123" and departmentCode="JBG", I expect the ID to use when SDR interface is called is abc123_JBG.
For example http://localhost/EmployeeDepartment/abc123_JBG should fetch me the result and indeed it does.
But, when I try to save an entity using PUT, the ID property available in BasicPersistentEntity class of Spring Data Commons is having a value of
abc123_JBG for departmentCode. This is wrong. I'm not sure if this is an expected behaviour.
Please help.
Thanks!
Currently Spring Data REST only supports compound keys that are represented as by a single field. That effectively means only #EmbeddedId is supported. I've filed DATAJPA-770 to fix that.
If you can switch to #EmbeddedId you still need to teach Spring Data REST the way you'd like to represent your complex identifier in the URI and how to transform the path segment back into an instance of your id type. To achieve that, implement a BackendIdConverter and register it as Spring bean.
#Component
class CustomBackendIdConverter implements BackendIdConverter {
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
// Make sure you validate the input
String[] parts = id.split("_");
return new YourEmbeddedIdType(parts[0], parts[1]);
}
#Override
public String toRequestId(Serializable source, Class<?> entityType) {
YourIdType id = (YourIdType) source;
return String.format("%s_%s", …);
}
#Override
public boolean supports(Class<?> type) {
return YourDomainType.class.equals(type);
}
}
If you can't use #EmbeddedId, you can still use #IdClass. For that, you need the BackendIdConverter as Oliver Gierke answered, but you also need to add a Lookup for your domain type:
#Configuration
public class IdClassAllowingConfig extends RepositoryRestConfigurerAdapter {
#Override
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
config.withEntityLookup().forRepository(EmployeeDepartmentRepository.class, (EmployeeDepartment ed) -> {
EmployeeDepartmentPK pk = new EmployeeDepartmentPK();
pk.setDepartmentId(ed.getDepartmentId());
pk.setEmployeeId(ed.getEmployeeId());
return pk;
}, EmployeeDepartmentRepository::findOne);
}
}
Use #BasePathAwareController to customize Spring data rest controller.
#BasePathAwareController
public class CustInfoCustAcctController {
#Autowired
CustInfoCustAcctRepository cicaRepo;
#RequestMapping(value = "/custInfoCustAccts/{id}", method = RequestMethod.GET)
public #ResponseBody custInfoCustAccts getOne(#PathVariable("id") String id) {
String[] parts = id.split("_");
CustInfoCustAcctKey key = new CustInfoCustAcctKey(parts[0],parts[1]);
return cicaRepo.getOne(key);
}
}
It's work fine for me with sample uri /api/custInfoCustAccts/89232_70
A more generic approach would be following -
package com.pratham.persistence.config;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sun.istack.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import javax.persistence.EmbeddedId;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Base64;
import java.util.Optional;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
* Customization of how composite ids are exposed in URIs.
* The implementation will convert the Ids marked with {#link EmbeddedId} to base64 encoded json
* in order to expose them properly within URI.
*
* #author im-pratham
*/
#Component
#RequiredArgsConstructor
public class EmbeddedBackendIdConverter implements BackendIdConverter {
private final ObjectMapper objectMapper;
#Override
public Serializable fromRequestId(String id, Class<?> entityType) {
return getFieldWithEmbeddedAnnotation(entityType)
.map(Field::getType)
.map(ret -> {
try {
String decodedId = new String(Base64.getUrlDecoder().decode(id));
return (Serializable) objectMapper.readValue(decodedId, (Class) ret);
} catch (JsonProcessingException ignored) {
return null;
}
})
.orElse(id);
}
#Override
public String toRequestId(Serializable id, Class<?> entityType) {
try {
String json = objectMapper.writeValueAsString(id);
return Base64.getUrlEncoder().encodeToString(json.getBytes(UTF_8));
} catch (JsonProcessingException ignored) {
return id.toString();
}
}
#Override
public boolean supports(#NonNull Class<?> entity) {
return isEmbeddedIdAnnotationPresent(entity);
}
private boolean isEmbeddedIdAnnotationPresent(Class<?> entity) {
return getFieldWithEmbeddedAnnotation(entity)
.isPresent();
}
#NotNull
private static Optional<Field> getFieldWithEmbeddedAnnotation(Class<?> entity) {
return Arrays.stream(entity.getDeclaredFields())
.filter(method -> method.isAnnotationPresent(EmbeddedId.class))
.findFirst();
}
}

Resources