How to add structural links to Jersey/Moxy/JAXB XML without altering the model - rest

I mean "structural links" in the HATEOAS/hypermedia API sense. The more general question is how to augment the generated XML with data that depends on both the entity being marshalled, and also on the environment (in this case, at least the absolute URL).
I'm using Jersey 2.9 with Moxy 2.5 as the JAXB provider.
From this model:
package testing;
import java.util.ArrayList;
import java.util.List;
public class Planet {
private int id = 1;
private String name = "test";
private double radius = 3.0;
private String href;
private List<Moon> moons = new ArrayList<Moon>(0);
public void addMoon(Moon moon) {
moons.add(moon);
}
}
...plus Moon class
I want to get something like this XML (and the equivalent JSON):
<planet href="http://mytestserver/rest/planets/test">
<name>test</name>
<radius>3.0</radius>
<moons>
<moon href="http://mytestserver/rest/moons/moon1">
<name>moon1</name>
</moon>
<moon href="http://mytestserver/rest/moons/moon2">
<name>moon2</name>
</moon>
</moons>
</planet>
The model has no "href" field, nor can one be added. Ideally I could use UriBuilder to grab these paths straight from the resource classes.
So far I've come up with several possiblities. Can I ask you to consider which (if any) has the most legs, and then how you would work around the shortcomings of that method?
1. Augment the model with AspectJ (or Javassist).
And then use the existing declarative linking mechanisms in Jersey, all of which rely on there being a field in the model to receive the generated links. This obviously won't work if you don't have AspectJ in your build process and/or balk at exotic techniques like byte code manipulation.
2. Post-process the generated XML and JSON
For example, in a MessageBodyWriter:
ContextResolver<JAXBContext> resolver = providers.getContextResolver(JAXBContext.class, mediaType);
JAXBContext context = resolver.getContext(type);
Marshaller m = context.createMarshaller();
<--- here, marshall to e.g. a DOM then transform that
<--- then manipulate the JSON structures
I have absolutely no idea how to do any of that, hence the lack of code. There may be other ways to hook into the XML generation process, but as far as I can see none of Jersey's or JAXB's event handlers or interceptors actually allow you to manipulate the generated XML/JSON.
3. Use a Moxy XMLTransformationMapping
For example:
XML binding:
<java-type name="Planet" xml-customizer="testing.HrefCustomizer">
Customizer:
public class HrefCustomizer implements DescriptorCustomizer {
#Override
public void customize(ClassDescriptor descriptor) throws Exception {
XMLTransformationMapping xtm = new XMLTransformationMapping();
xtm.addFieldTransformer("#href", new HrefWriter());
descriptor.addMapping(xtm);
}
}
Transformer:
public class HrefWriter implements FieldTransformer {
#Override
public Object buildFieldValue(Object instance, String fieldName,
Session session) {
return "href"; // constant value just for proof-of-concept
}
#Override
public void initialize(AbstractTransformationMapping mapping) {
// TODO Auto-generated method stub
}
}
I have two problems with this approach:
It was so hard to find any documentation on it that I wonder if it is in fact unsupported usage.
I can't see how the transformer is going to get a UriBuilder to work with. At minimum it would need the root URL of the rest service.
4. Slightly different Moxy xml-transform approach
If we decide we can't provide the transformer with any meaningful context at instantiation time, the customizer is adding no value and we can simplify the above to just this:
<java-type name="Planet">
<xml-root-element/>
<java-attributes>
<xml-transformation java-attribute="name">
<xml-write-transformer transformer-class="testing.HrefWriter" xml-path="#href"/>
</xml-transformation>
<xml-element java-attribute="name"/>
With the slight oddity that we are hanging the transformer off another field ("name", in this example).
5. ?????
Or, I'm completely barking up the wrong tree. Help!!

AspectJ approach
Synopsis
Use AspectJ to add a field to the model classes (called "href" in this example)
Add the Jersey #InjectLink annotation to that field
Jersey will then populate the field with the right URL as defined by the resource class
Specify the marshaling of the href field using an external mapping file.
You could also specify the marshaling of href by adding JAXB annotations to it via the same AspectJ intertype declaration mechanism.
Example code
These are the most informative bits. See http://lagod.id.au/blog/?p=494 for the full example.
The aspect
package testing;
import org.glassfish.jersey.linking.InjectLink;
import org.glassfish.jersey.linking.Binding;
public aspect HrefInjector {
private String Planet.href;
declare #field : * Planet.href : #InjectLink(
resource=Services.class,
style=InjectLink.Style.ABSOLUTE
) ;
private String Moon.href;
declare #field : * Moon.href : #InjectLink(
resource=Services.class,
method="moon",
bindings={#Binding(
name="moonid", value="${instance.name}"
)},
style=InjectLink.Style.ABSOLUTE
) ;
}
Model classes
POJOs with no REST-specific cruft. See Jersey + Moxy + JAXB - how to marshal XML without annotations.
package testing;
import java.util.ArrayList;
import java.util.List;
public class Planet {
private int id = 1;
private String name = "test";
private double radius = 3.0;
private List<Moon> moons = new ArrayList<Moon>(0);
public void addMoon(Moon moon) {
moons.add(moon);
}
}
package testing;
public class Moon {
private String name;
// No-arg constructor is a requirement of JAXB
public Moon() {
}
public Moon(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
Resource class
This is a standard JAX-RS resource class. For demo purposes, we're just returning freshly instantiated model instances.
package testing;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
#Path("/services")
#Produces({MediaType.APPLICATION_XML,MediaType.APPLICATION_JSON})
public class Services {
private Planet initPlanet() {
Planet p = new Planet();
p.addMoon(new Moon("moon1"));
p.addMoon(new Moon("moon2"));
return p;
}
#GET
public Planet planet () {
return initPlanet();
}
#GET #Path("/moons/{moonid}")
public Moon moon (#PathParam("moonid") String name) {
return new Moon(name);
}
}
Moxy mapping file
Note that you can choose for any given type whether or not you want to actually marshal the href field. In fact, by using multiple mapping files, you can include the href field in some representations and not in others.
<?xml version="1.0"?>
<xml-bindings
xmlns="http://www.eclipse.org/eclipselink/xsds/persistence/oxm"
package-name="testing"
xml-mapping-metadata-complete="true"
xml-accessor-type="NONE">
<java-types>
<java-type name="Planet">
<xml-root-element/>
<java-attributes>
<xml-attribute java-attribute="href"/>
<xml-element java-attribute="name"/>
<xml-element java-attribute="radius"/>
<xml-element java-attribute="moons" name="moon">
<xml-element-wrapper name="moons"/>
</xml-element>
</java-attributes>
</java-type>
<java-type name="Moon">
<xml-root-element/>
<java-attributes>
<xml-attribute java-attribute="href"/>
<xml-element java-attribute="name"/>
</java-attributes>
</java-type>
</java-types>
</xml-bindings>
Sample output
Ta-dah! Structural links derived automatically from the JAX-RS resource class without altering model source code. Because we're using Moxy, we also get JSON for free.
<planet href="http://localhost:8080/reststructlinks/rest/services">
<name>test</name>
<radius>3.0</radius>
<moons>
<moon href="http://localhost:8080/reststructlinks/rest/services/moons/moon1">
<name>moon1</name>
</moon>
<moon href="http://localhost:8080/reststructlinks/rest/services/moons/moon2">
<name>moon2</name>
</moon>
</moons>
</planet>

Related

Mapping from map to bean using snake case to camel case strategy

I need to convert an object of Map<String,String> with keys like "some_att_name" to class object fields like someAttName.
I couldn't find an easy way to do this.
MapStruct does support this type of mapping (From Map to object) since v1.5.0.Beta1 as stated here.
What I want should look something like this (similar to how JSON converters work):
#Mapper
public interface MapToObjectMapper {
MapToObjectMapper INSTANCE = Mappers.getMapper(MapToObjectMapper.class);
#Mapping(strategy = SnakeCaseToCamelCaseStrategy.class)
MyObject toMyObject(Map<String,String> map);
}
You'll have to translate the keys by yourself, but that's not that hard. Here is how I do it:
import org.mapstruct.Mapper;
import org.mapstruct.factory.Mappers;
import java.util.Collections;
import java.util.Map;
import java.util.regex.Pattern;
import static java.util.stream.Collectors.collectingAndThen;
import static java.util.stream.Collectors.toMap;
#Mapper
public interface MapToObjectMapper {
MapToObjectMapper INSTANCE = Mappers.getMapper(MapToObjectMapper.class);
private static String snakeToCamel(String snakeCaseString) {
// You can use Guava, Apache Commons, write it yourself or just use this one
// Credits to https://stackoverflow.com/a/67605103/4494577
return Pattern.compile("_([a-z])")
.matcher(snakeCaseString)
.replaceAll(m -> m.group(1).toUpperCase());
}
MyObject toMyObject(Map<String, String> map);
default MyObject toMyObjectFromSnakeCaseMap(Map<String, String> snakeKeyPropertyMap) {
return toMyObject(snakeKeyPropertyMap.entrySet().stream()
.collect(collectingAndThen(
toMap(s -> snakeToCamel(s.getKey()), Map.Entry::getValue),
Collections::unmodifiableMap)));
}
}
Full example: https://github.com/jannis-baratheon/stackoverflow--mapstruct-snake-case-map-mapping/
Looking at the docs I can't see a 1 step mapping. I think referring to From snake_case to camelCase in Java for converting your Map's keys to camelCase and then going for a mapper without #Mapping annotations might be your best chance.

Spring boot Rest API Request Body (POST)

Request Body has one parameter such as int data. Where as if i pass extra parameter which is not there in the class it does not throw an error.
Why its able to consume the data which is not part of request body.
By default spring will recognize only the declared request params remaining parameters will be ignored. If you want to restrict it. you can do like this
Add HttpServletRequest request in the method parameter
String params = request.getQueryString();
in the method body validate the params.
But In my opinion a more pragmatic approach may be to ignore the invalid params.
Having used RESTful APIs from numerous vendors over the years, let me give you a "users" perspective.
A lot of times documentation is simply bad or out of date. Maybe a parameter name changed, maybe you enforce exact casing on the property names, maybe you have used the wrong font in your documentation .
So by default , REST API doesn't check for the extra attributes not presents in request body; otherwise checks whether the supplied attributes is present or not.
if you want not to deserialise the property which is not present; you can add validations as follows :
import java.io.Serializable;
import javax.validation.constraints.NotBlank;
public class DemoDto implements Serializable{
private static final long serialVersionUID = 1L;
private int id;
#NotBlank(message = "name can't be empty.")
private String name;
//getters & setters
}
Controller :
import javax.validation.Valid;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class TestController {
#PostMapping("/test")
public ResponseEntity<DemoDto> test(#RequestBody #Valid DemoDto demoDto){
return ResponseEntity.ok(demoDto);
}
}

CXF #CrossOriginResourceSharing Annotation

I am using CXF/Karaf, and I have many different RESTful resources within multiple packages/classes.
Question: Is there a way to avoid having to write annotations above every resource, such as #CrossOriginResourceSharing?
I would like to be able to put this in one place. Below is an example of how this is currently being used:
#CrossOriginResourceSharing(
allowOrigins = {"http://<ip>:<port>"}
)
#GET
#Path("/rest")
#Produces(MediaType.APPLICATION_JSON)
public String rest();
You can add this to the class instead of on each resource in the class (and I haven't tested this but you could possible create a super class with this annotation and extend it but I'm not sure if CXF will honour it):
#CrossOriginResourceSharing(
allowOrigins = {"http://<ip>:<port>"}
)
public class resourceGroup {
#GET
#Path("/rest")
#Produces(MediaType.APPLICATION_JSON)
public String rest();
}

Losing type element when serializing object inside ArrayList to XML

I seem to be experiencing a problem when using Jackson to serialize to XML. My code is below:
TEST CONTAINER
package com.test;
import java.util.ArrayList;
import com.fasterxml.jackson.annotation.JsonProperty;
public class TestContainer {
private String testContainerID;
private String testContainerMessage;
private ArrayList<TestChild> testContainerChildren;
#JsonProperty("TestContainerID")
public String getTestContainerID() {
return testContainerID;
}
#JsonProperty("TestContainerID")
public void setTestContainerID(String testContainerID) {
this.testContainerID = testContainerID;
}
#JsonProperty("TestContainerMessage")
public String getTestContainerMessage() {
return testContainerMessage;
}
#JsonProperty("TestContainerMessage")
public void setTestContainerMessage(String testContainerMessage) {
this.testContainerMessage = testContainerMessage;
}
#JsonProperty("TestContainerChildren")
public ArrayList<TestChild> getTestContainerChildren() {
return testContainerChildren;
}
#JsonProperty("TestContainerChildren")
public void setTestContainerChildren(ArrayList<TestChild> testContainerChildren) {
this.testContainerChildren = testContainerChildren;
}
}
TESTCHILD
package com.test;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
#JsonRootName(value="TestChild")
public class TestChild {
private String testChildID;
private String testChildMessage;
#JsonProperty("TestChildID")
public String getTestChildID() {
return testChildID;
}
#JsonProperty("TestChildID")
public void setTestChildID(String testChildID) {
this.testChildID = testChildID;
}
#JsonProperty("TestChildMessage")
public String getTestChildMessage() {
return testChildMessage;
}
#JsonProperty("TestChildMessage")
public void setTestChildMessage(String testChildMessage) {
this.testChildMessage = testChildMessage;
}
}
USE
Serialization:
XmlMapper xm = new XmlMapper();
TestContainer tc = xm.readValue(sb.toString(), TestContainer.class);
Deserialization:
System.out.println(xm.writeValueAsString(tc));
tc = xm.readValue(sb.toString(), TestContainer.class);
What I'm doing is loading an XML file from a folder on the classpath and putting the contents of the file into a StringBuffer. The problem is the generated XML for the collection of objects. When writing the XML, I want something like:
<TestContainerChildren><TestChild><...(Element Details)...></TestChild></TestContainerChildren>
but I'm getting:
<TestContainerChildren><TestContainerChildren><...(Element Details)...><TestContainerChildren></TestContainerChildren>
I'm not sure what I'm missing, here. I have no problem with the JSON part of the serialization/deserialization, only the XML. I've tried using both Jackson and JAXB annotations to turn off wrapping, I have tried using the following annotations:
#JsonRootName
#JsonProperty
#JacksonXmlElementWrapper
#JacksonElement
#XmlElementWrapper
#XmlElement
I'm pretty sure this is something stupid on my part, but any help would be most appreciated.
Ok, couple of notes. First, #JsonRootName only affects name used for the root of XML document, as name implies. So it is not used for TestChild. Second, it sounds like you want to use so-called "unwrapped" output for Lists, omitting element for property that contains List elements. This is doable with:
#JacksonXmlElementWrapper(useWrapping=false)
#JsonProperty("TestContainerChildren")
public ArrayList<TestChild> getTestContainerChildren() { ... }
since default setting is to use wrapper (this is different from JAXB, where unwrapped is the default). Or, if you want to change this globally to assume unwrapped as default, you can change the defaults via XmlModule:
JacksonXmlModule module = new JacksonXmlModule();
// to default to using "unwrapped" Lists:
module.setDefaultUseWrapper(false);
XmlMapper xmlMapper = new XmlMapper(module);
Hope this helps!
I got this working by using the following annotations above the variable declaration:
#JacksonXmlElementWrapper(localName="[insert collection name]")
#JacksonXmlProperty(localName="[insert collection element name]")
This was a simple case of RTFM, as it's documented here.

MEF: how to import from an exported object?

I have created a MEF plugin control that I import into my app. Now, I want the plugin to be able to import parts from the app. I can't figure how setup the catalog in the plugin, so that it can find the exports from the app. Can somebody tell me how this is done? Below is my code which doesn't work when I try to create an AssemblyCatalog with the current executing assembly.
[Export(typeof(IPluginControl))]
public partial class MyPluginControl : UserControl, IPluginControl
[Import]
public string Message { get; set; }
public MyPluginControl()
{
InitializeComponent();
Initialize();
}
private void Initialize()
{
AggregateCatalog catalog = new AggregateCatalog();
catalog.Catalogs.Add(new AssemblyCatalog(Assembly.GetExecutingAssembly()));
CompositionContainer container = new CompositionContainer(catalog);
try
{
container.ComposeParts(this);
}
catch (CompositionException ex)
{
Console.WriteLine(ex.ToString());
}
}
}
You don't need to do this.
Just make sure that the catalog you're using when you import this plugin includes the main application's assembly.
When MEF constructs your type in order to export it (to fulfill the IPluginControl import elsewhere), it'll already compose this part for you - and at that point, will import the "Message" string (though, you most likely should assign a name to that "message", or a custom type of some sort - otherwise, it'll just import a string, and you can only use a single "string" export anywhere in your application).
When MEF composes parts, it finds all types matching the specified type (in this case IPluginControl), instantiates a single object, fills any [Import] requirements for that object (which is why you don't need to compose this in your constructor), then assigns it to any objects importing the type.