Translate XPath to JXPath - xpath

I've got the following XML:
<?xml version="1.0" encoding="UTF-8"?>
<CreateReservation>
<NewReservation>
<Profile op="D">
<ProfileID>ID</ProfileID>
<ProfileType>TYPE</ProfileType>
</Profile>
<Number>123456</Number>
</NewReservation>
marshalled with JAXB from CreateReservation class in the following way:
CreateReservation request = new CreateReservation("123456", "D");
String xpathExpr = "boolean(//*[local-name()='CreateReservationRQ']//#op='D')"
JAXBContext context = JAXBContext.newInstance(CreateReservation.class);
marshaller = context.createMarshaller();
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
DocumentBuilder db = dbf.newDocumentBuilder();
Document document = db.newDocument();
marshaller.marshal(request, document); //line creates xml presented above
//EVALUATION
XPath xPath = XPathFactory.newInstance().newXPath();
xPath.evaluate(xpathExpr, document, XPathConstants.BOOLEAN); //line evaluates xpath and returns true
Summing everythig up, presented code marshalls CreateReservation class, create corresponding XML out of it, and with xpath expression checks if created xml has any <CreateReservation> node, which has any child with op="D" attribute. The check is done with the following xpath expression:
boolean(//*[local-name()='CreateReservationRQ']//#op='D')
The condition works fine, fot presented code sample. Right now I would like to get rid of unnecessary marshalling of CreateReservation class. To do so, I would like to switch from JAXB to JXPath:
PathContext jXPathContext = JXPathContext.newContext(obj);
Object obj = jXPathContext.getValue(CONDITION);
if (obj != null) {
//code should behave exactly the same, as my previous model
}
obj- member of CreateReservation class (it's structure is exactly the same as presented in xml above)
CONDITION- my JXPath query
The idea is, that line:
Object obj = jXPathContext.getValue(CONDITION);
Returns any node, when CreateREservation class has op field equal to 'D'. If no node is present null is returned. Functionally code should behave exactly the same as previously. Could you tell me how my CONDITION should look like?

For dynamic xpath to object implementation you can have a look into this.
Example 1: JavaBean Property Access
JXPath can be used to access properties of a JavaBean.
public class Employee {
public String getFirstName(){
...
}
}
Employee emp = new Employee();
JXPathContext context = JXPathContext.newContext(emp);
String fName = (String)context.getValue("firstName");
In this example, we are using JXPath to access a property of the emp bean. In this simple case the invocation of JXPath is equivalent to invocation of getFirstName() on the bean.
Example 2: Nested Bean Property Access
JXPath can traverse object graphs:
public class Employee {
public Address getHomeAddress(){
...
}
}
public class Address {
public String getStreetNumber(){
...
}
}
Employee emp = new Employee();
...
JXPathContext context = JXPathContext.newContext(emp);
String sNumber = (String)context.getValue("homeAddress/streetNumber");
In this case XPath is used to access a property of a nested bean.
A property identified by the xpath does not have to be a "leaf" property. For instance, we can extract the whole Address object in above example:
Address addr = (Address)context.getValue("homeAddress");
Example 3: Setting Properties
JXPath can be used to modify property values.
public class Employee {
public Address getAddress() {
...
}
public void setAddress(Address address) {
...
}
}
Employee emp = new Employee();
Address addr = new Address();
...
JXPathContext context = JXPathContext.newContext(emp);
context.setValue("address", addr);
context.setValue("address/zipCode", "90190");
Example 4: Creating objects
JXPath can be used to create new objects. First, create a subclass of AbstractFactory and install it on the JXPathContext. Then call createPathAndSetValue() instead of "setValue". JXPathContext will invoke your AbstractFactory when it discovers that an intermediate node of the path is null. It will not override existing nodes.
public class AddressFactory extends AbstractFactory {
public boolean createObject(JXPathContext context,
Pointer pointer, Object parent, String name, int index){
if ((parent instanceof Employee) && name.equals("address"){
((Employee)parent).setAddress(new Address());
return true;
}
return false;
}
}
JXPathContext context = JXPathContext.newContext(emp);
context.setFactory(new AddressFactory());
context.createPathAndSetValue("address/zipCode", "90190");
For detail help look into this link: JxPath Tutorial

Related

Spring expression language: Using a variable as key for a map lookup in SpEL

I'm trying to use variable as key to look up a value in a map. I would like to be able to reference the variable directly (in this case jobTitle), but for some reason I need to prefix the variable with either #root. or #this. in order to get it to work. So this works:
parser.parseExpression("{ \"Manager\":\"37.5\", \"CEO\":\"40\"}[#root.jobTitle]"
(resolves to "37.5")
but this doesn't
parser.parseExpression("{ \"Manager\":\"37.5\", \"CEO\":\"40\"}[jobTitle]"
(resolves to null)
jobTitle is a root attribute on the context object. From looking at the SpEL docs it seems like I should be able to reference the attribute directly? Am I doing something wrong?
Full working code below
public static void main(String[] args) {
ExpressionParser parser = new SpelExpressionParser();
Employee employee = new Employee("Joe Bloggs", "Manager");
Expression exp = parser.parseExpression(
"{ \"Manager\":\"37.5\", \"Ceo\":\"40\"}[#root.jobTitle]");
StandardEvaluationContext context = new StandardEvaluationContext(employee);
context.setRootObject(employee);
System.out.println(exp.getValue(context, String.class));
}
static class Employee {
private String firstName;
private String jobTitle;
Employee(String firstName, String jobTitle) {
this.firstName = firstName;
this.jobTitle = jobTitle;
}
public String getJobTitle() {
return jobTitle;
}
}
From looking at the SpEL docs it seems like I should be able to reference the attribute directly?
This is correct for below case, both will print "Manager"
System.out.println(parser.parseExpression("#root.jobTitle").getValue(context, String.class));
System.out.println(parser.parseExpression("jobTitle").getValue(context, String.class));
But for expression inside [], the handling is different. Since there is no language specification for SpEL, I can just explain base on the behaviour.
When you run
System.out.println(parser.parseExpression(
"{\"jobTitle\":\"37.5\"}[jobTitle]").getValue(context, String.class));
This will actually print "37.5"([jobTitle] is treated as [\"jobTitle\"]). The author decided to make it easier to access value of Map, and the drawback is you need to specify with #root.jobTitle when referencing the root object.

Convert Long id to Set list using Mapstruct?

In a Mapstruct interface, how can I convert an id to a Set< UserSystem > ?
I tried as follows but unsuccessfully because error occurs:
#Mapper(componentModel = "spring", uses = {UserSystemService.class})
public interface CompanyPostMapper extends EntityMapper<CompanyPostDTO, Company> {
#Mapping(source = "userSystemId", target = "userSystems", expression = "java(userSystemService.findByIdAndAddToSet(id))")
Company toEntity(CompanyPostDTO dto);
default Company fromId(Long id) {
if (id == null) {
return null;
}
Company company = new Company();
company.setId(id);
return company;
}
}
I don't know if I understood the use of the "uses" parameter correctly, but basically I would like to get the ID and query the register and return a Set with the register.
I was going to try "qualifiedByName" and create a method in the Mapper interface, but I don't know how I can inject the Repository and I don't know if that would be a good practice.
What would be the best way to solve?
MapStruct is a mapping framework. You are doing a lookup inside the mapping. It is possible of course (look at the JPA mapping example where an #Context is used). But you can't inherit EntityMapper<CompanyPostDTO, Company> at the same time.
Normally, you need to take the lookup outside your mapping logic and use an update method to update the object.
Your mapping would then look like:
#Mapper(componentModel = "spring" )
public interface CompanyPostMapper {
void updateEntity(CompanyPostDTO dto, #MappingTarget Company entity);
}
// and your call would look like:
public class CallingService{
Company company = userSystemService.findByIdAndAddToSet(id));
if (company == null) {
company = new Company();
}
companyPostMapper.updateEntity(dto, company);
}

Mapping List of Object From Parent Object which has a List of Objects

I am trying to use mapstruct to transform an object as below
Source
MainObject
{
String key;
List<ChildObject> children;
}
ChildObject{
String childVar1;
String childVar2;
}
Target
List<TargetObj> targetObjects;
TargetObj{
String key;
String var1;
String var2;
}
I need to prepare a list of TargetObj instances with the key mapped from the key from MainObject and var1 and var2 mapped from ChildObject.
I tried to use ObjectFactory and Decorator as mentioned in the mapstruct documentation. But couldn't find a way to get this done. Both cases I got an error which states cannot return iterable object from non iterable parameters.
You can try and use a combination of #BeforeMapping or #AfterMapping with the #Context.
Your mapper can look like:
#Mapper
public interface MyMapper {
default List<TargetObj> map(MainObject source) {
if (source == null) {
return Collections.emptyList(); // or null or whatever you prefer
}
return map(source.getChildren(), new CustomContext(source));
}
List<TargetObject> map(List<ChildObject> children, #Context CustomContext context);
#Mapping(target = "key", ignore = true) // key is mapped in the context
TargetObject map(ChildObject child, #Context CustomContext context);
}
And the custom context would look something like:
public class CustomContext {
protected final MainObject mainObject;
public CustomContext(MainObject mainObject) {
this.mainObject = mainObject;
}
#AfterMapping // of #BeforeMapping
public void afterChild(#MappingTarget ChildObject child) {
child.setKey(mainObject.getKey());
// More complex mappings if needed
}
}
The goal is to do manual mapping from your MainObject to the List<TargetObj> by using other methods that MapStruct will generate

How can I use a local variable in the annotation #Preauthorize?

i need to do something like this
String myVar = "myString";
...
#Preauthorize("customMethod(myVar)")
public void myMethod() {
...
}
but I'm failing at it. How can I do that? It says it cannot be resolved
EDIT:I'm decoupling few rest services and sometimes I have to share infos between them
#Value("${my-properties}")
String urlIWantToShare;
...
#PreAuthorize("isValid(#myValue,urlIWantToShare)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue)) {
return true;
}
this "isValid" custom security method will call an external service, that doesn't know anything about the caller and his infos. I need to transmit few infos and I need to take them from different kind of sources
One of the sources is my application.properties
EDIT2: I managed to do this
#PreAuthorize("isValid(#myValue, #myProperty)")
#RequestMapping(value = "**/letsCheckSecurityConfig", method = RequestMethod.GET)
public boolean letsCheckSecurityConfig(#RequestHeader(name = "MY-VALUE") String myValue,
#Value("${my-property-from-app-properties}") String myProperty))
..but I want to use not only actual static properties but runtime one. Any help?
You can create a wrapper method without parameters which will call the desired method with parameters. In the annotation you can use the method without parameters
Apologies if I have misunderstood what you are trying to do, but from my understanding you're trying to set an annotation at runtime based on a variable / app.properties, so that you can then read this variable and then execute your class?
If this is the case, You cannot do this from an annotation alone as annotations cannot read local variables and cannot be set at runtime.
However, one option for you is to have an object which contains the 'values' of interest for you and then read the values from the object.
Something like the below:
PoJo
public class testObject{
#test
private String myVar;
private String myValue;
//Getters and Setters
}
Get Object values
public void getFields (Object obj){
Field fields = obj.getClass().getDeclaredFields();
for (Field f : fields){
test fieldAnnotation = f.getAnnotation(test.Class);
if (fieldAnnotation != null){
f.get(obj);
// Do checks based on this
}
}
}
Main Class
public static void main(String[] args){
//Create object
testObject test = new testObject();
test.setOne("testOne");
test.setTwo("testTwo");
getFields(test);
}
I've pulled this code based on what I had to do to get the fields - but in my case, I did not know the object types I was going to be passed. You are simply using the annotation to 'mark' the fields you want to retrieve and then reading the value from the object.
If you're in a similar situation, then you can see my answer here: initial answer
Let me know if i've misunderstood this and I can try and further clarify my answer.

How do you handle deserializing empty string into an Enum?

I am trying to submit a form from Ext JS 4 to a Spring 3 Controller using JSON. I am using Jackson 1.9.8 for the serialization/deserialization using Spring's built-in Jackson JSON support.
I have a status field that is initially null in the Domain object for a new record. When the form is submitted it generates the following json (scaled down to a few fields)
{"id":0,"name":"someName","status":""}
After submitted the following is seen in the server log
"nested exception is org.codehaus.jackson.map.JsonMappingException: Can not construct instance of com.blah.domain.StatusEnum from String value '': value not one of the declared Enum instance names"
So it appears that Jackson is expecting a valid Enum value or no value at all including an empty string. How do I fix this whether it is in Ext JS, Jackson or Spring?
I tried to create my own ObjectMapper such as
public class MyObjectMapper extends Object Mapper {
public MyObjectMapper() {
configure(DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true);
}
}
and send this as a property to MappingJacksonMappingView but this didn't work. I also tried sending it in to MappingJacksonHttpMessageConverter but that didn't work. Side question: Which one should I be sending in my own ObjectMapper?
Suggestions?
The other thing you could do is create a specialized deserializer (extends org.codehaus.jackson.map.JsonDeserializer) for your particular enum, that has default values for things that don't match. What I've done is to create an abstract deserializer for enums that takes the class it deserializes, and it speeds this process along when I run into the issue.
public abstract class EnumDeserializer<T extends Enum<T>> extends JsonDeserializer<T> {
private Class<T> enumClass;
public EnumDeserializer(final Class<T> iEnumClass) {
super();
enumClass = iEnumClass;
}
#Override
public T deserialize(final JsonParser jp,
final DeserializationContext ctxt) throws IOException, JsonProcessingException {
final String value = jp.getText();
for (final T enumValue : enumClass.getEnumConstants()) {
if (enumValue.name().equals(value)) {
return enumValue;
}
}
return null;
}
}
That's the generic class, basically just takes an enum class, iterates over the values of the enum and checks the next token to match any name. If they do it returns it otherwise return null;
Then If you have an enum MyEnum you'd make a subclass of EnumDeserializer like this:
public class MyEnumDeserializer extends EnumDeserializer<MyEnum> {
public MyEnumDeserializer() {
super(MyEnum.class);
}
}
Then wherever you declare MyEnum:
#JsonDeserialize(using = MyEnumDeserializer.class)
public enum MyEnum {
...
}
I'm not familiar with Spring, but just in case, it may be easier to handle that on the client side:
Ext.define('My.form.Field', {
extend: 'Ext.form.field.Text',
getSubmitValue: function() {
var me = this,
value;
value = me.getRawValue();
if ( value === '' ) {
return ...;
}
}
});
You can also disallow submitting empty fields by setting their allowBlank property to false.
Ended up adding defaults in the EXT JS Model so there is always a value. Was hoping that I didn't have to this but it's not that big of a deal.
I have the same issue. I am reading a JSON stream with some empty strings. I am not in control of the JSON stream, because it is from a foreign service. And I am always getting the same error message. I tried this here:
mapper.getDeserializationConfig().with(DeserializationConfig.Feature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
But without any effect. Looks like a Bug.

Resources