Search through XMLs with Java 8 - search

I need to create a tool in java 8 to search through a list of .xml files that are all in the same folder. Each file contains information of this type:
<id>135719887</id>
<FactValue>
<name>FACT.DOCUMENT.TOTAL_DUE_AMOUNT</name>
<valueString>832.15</valueString>
</FactValue>
<FactValue>
<name>FACT.DOCUMENT.AUTO_PAY</name>
<valueString>false</valueString>
</FactValue>
<FactValue>
<name>FACT.DOCUMENT.CREDIT_CARD_EXPIRED</name>
<valueString>false</valueString>
</FactValue>
<FactValue>
<name>FACT.DOCUMENT.HAS_SEDONA_CHARGES</name>
<valueString>true</valueString>
</FactValue>
what required is a tool which can extract from a single or multiple search/query, the FACT code and its value in the following way:
id type value
---------- -------------------- -------
135719887 TOTAL_DUE_AMOUNT 832.15
135719887 AUTO_PAY false
135719887 CREDIT_CARD_EXPIRED false
135719887 HAS_SEDONA_CHARGES true
The queries would be, for instance:
Which Ids have the CREDIT_CARD_EXPIRED = false, AND, AUTO_PAY = true
Which Ids have the CREDIT_CARD_EXPIRED = true, OR, AUTO_PAY = false
What is the value of TOTAL_DUE_AMOUNT fact of the Id xxxxxxxxx
I am interested in a couple of things:
1- What data structure to use
2- How to iterate all the files
Performance is not as importat as flexibility.
Thanks in advance

Let's suppose that FooDto is the POJO class for your XML content, then you can do something like:
public static void main(String[] args) throws Exception {
JAXBContext jaxbContext = JAXBContext.newInstance(FooDto.class);
Unmarshaller jaxbUnmarshaller = jaxbContext.createUnmarshaller();
FooDto fooDto1 = (FooDto) jaxbUnmarshaller.unmarshal(getInput("/Foo1.xml"));
FooDto fooDto2 = (FooDto) jaxbUnmarshaller.unmarshal(getInput("/Foo2.xml"));
List<String> ids = Stream.of(fooDto1, fooDto2)
.filter(Main::creditCardIsNotExpired)
.map(FooDto::getId)
.collect(Collectors.toList());
}
private static boolean creditCardIsNotExpired(FooDto foo) {
return Arrays.stream(foo.getFactValue())
.anyMatch(fact -> fact.getName().equals("FACT.DOCUMENT.CREDIT_CARD_EXPIRED")
&& fact.getValueString().equals("false"));
}
This example gives you all ids for CREDIT_CARD_EXPIRED == false.
Use a DTO, which can be generated from the XML using IDE plugins or other tools.
Read files in a loop and create a collection of DTOs then apply your filters.

Related

Mapstruct: HashMap<String, Object> as source to Object

How could I use a HashMap<String, MyObjectSource> as source to an object?
Here is my target object:
public class QuantityDTO{
private Integer shoes;
private Integer pants;
}
Here is my object source:
public class Product{
private String name;
private Integer quantity;
}
Here is my map<String, Product> :
<("shoes", Product), ("pants", Product)>
I've tried to use this approach, but it's failing for me.
My Mapper:
#Mapping(target = "shoes", source = "shoes.quantity")
#Mapping(target = "pants", source = "pants.quantity")
QuantityDTO mapToQuantityDto(Map<String, Product> map);
The problem here is that Map to Bean mappings and nested source mappings do not combine that well. The problem with this is that MapStruct can not know whether the . is meant as a nested mapping identifier or if it is part of the key for the map itself. Both options are valid.
The behaviour to allow '.' as part of the key is listed here as a bug.
My suggestion would be to write out the mapping yourself with something like:
#Mapping(target = "shoes", source = "shoes")
#Mapping(target = "pants", source = "pants")
QuantityDTO mapToQuantityDto(Map<String, Product> map);
default Integer ProductToQuantity(Product product){
return product == null ? null : product.getQuantity();
}
This way the source field only contains the key, and the rest of the mapping is done at the default method.
For more complex mappings you could add another mapping annotated method to handle the complexity.
Also it would be useful to know what the message is that you get as a reason for it failing, or if you do not get a message what the mapstruct generated code looks like.

Cucumber V5-V6 - passing complex object in feature file step

So I have recently migrated to v6 and I will try to simplify my question
I have the following class
#AllArgsConstructor
public class Songs {
String title;
List<String> genres;
}
In my scenario I want to have something like:
Then The results are as follows:
|title |genre |
|happy song |romance, happy|
And the implementation should be something like:
#Then("Then The results are as follows:")
public void theResultsAreAsFollows(Songs song) {
//Some code here
}
I have the default transformer
#DefaultParameterTransformer
#DefaultDataTableEntryTransformer(replaceWithEmptyString = "[blank]")
#DefaultDataTableCellTransformer
public Object transformer(Object fromValue, Type toValueType) {
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.convertValue(fromValue, objectMapper.constructType(toValueType));
}
My current issue is that I get the following error: Cannot construct instance of java.util.ArrayList (although at least one Creator exists)
How can I tell cucumber to interpret specific cells as lists? but keeping all in the same step not splitting apart? Or better how can I send an object in a steps containing different variable types such as List, HashSet, etc.
If I do a change and replace the list with a String everything is working as expected
#M.P.Korstanje thank you for your idea. If anyone is trying to find a solution for this here is the way I did it as per suggestions received. Inspected to see the type fromValue has and and updated the transform method into something like:
if (fromValue instanceof LinkedHashMap) {
Map<String, Object> map = (LinkedHashMap<String, Object>) fromValue;
Set<String> keys = map.keySet();
for (String key : keys) {
if (key.equals("genres")) {
List<String> genres = Arrays.asList(map.get(key).toString().split(",", -1));
map.put("genres", genres);
}
return objectMapper.convertValue(map, objectMapper.constructType(toValueType));
}
}
It is somehow quite specific but could not find a better solution :)

Random string in cucumber scenarios

I am testing a GUI using cucumber. I need to test CRUD operations of the GUI.
When I write a scenario to create a new entity in GUI, I am unable to run multiple times, since the second time scenario fails because the ID I specified for the entity already exists (created in the first run) in the system the second time I run the test.
The system I am testing doesn't allow deleting entities. System needs to be started in a special mode to delete entities, so deleting the entity created after the test is not an option.
It would be great if I could use a random number for the entity id. For an example:
when user creates a new Branch with following values:
|Branch ID|<random_string_1>|
|Address|1, abc, def.|
|Telephone|01111111111|
And user searches for a branch by "Branch ID" = "<random_string_1>"
Then branch details should be as following
|Branch ID|<random_string_1>|
|Address|1, abc, def.|
|Telephone|01111111111|
Is there an option in cucumber to do something like this? Or, is there any other way I can achieve this?
In the end, I've added RandomStringTransformer class to test suite:
public class RandomStringTransformer extends Transformer<String> {
private static final Map<String, String> RANDOM_STRINGS = new HashMap<>(); //Key -> random string
public static final RandomStringTransformer INSTANCE = new RandomStringTransformer();
#Override
public String transform(String input) {
return transformString(input);
}
public DataTable transform(DataTable dataTable) {
dataTable.getGherkinRows().forEach(dataTableRow -> dataTableRow.getCells().replaceAll(this::transformString));
return dataTable;
}
private String transformString(String input) {
final String[] inputCopy = {input};
Map<String, String> replacements = new HashMap<>();
Matcher matcher = Pattern.compile("(<random_string_[^>]*>)").matcher(input);
while (matcher.find()) {
String group = matcher.group(0);
replacements.put(group, RANDOM_STRINGS.computeIfAbsent(group, key -> Utilities.getNextUniqueString()));
}
replacements.forEach((key, value) -> inputCopy[0] = inputCopy[0].replace(key, value));
return inputCopy[0];
}
}
And used the transformer in step definition:
#When("^user creates a branch of name "([^"]*)" with following values$")
public void branchIsCreatedWithDetails(#Transform(RandomStringTransformer.class) String branchName, DataTable fieldValues) {
fieldValues = RandomStringTransformer.INSTANCE.transform(fieldValues);
//Now, fieldValues table values and branchName are replaced with random values if they were in format <random_string_SOMETHING>
}
The #Transform annotation is not supported in Cucumber 3 anymore.
You have to transform data manually in the method body.
#When("^user creates a branch of name "([^"]*)" with following values$")
public void branchIsCreatedWithDetails(String branchName, DataTable fieldValues) {
fieldValues = RandomStringTransformer.INSTANCE.transform(fieldValues);
//Now, fieldValues table values and branchName are replaced with random values if they were in format <random_string_SOMETHING>
}
Read this for more information to migrate: http://grasshopper.tech/98/

how effciently read soap web service response with nested objects using streams

I consider a very easy task parse a nested object asnwered by a soap webservice using Java 8 streams. Nevertheless, I am quite confused when I think about the correct or most appropriate approach to use. I know it will depend on circunstances and there will never be a simple recipe. I have been reading for the last two weeks where and how to use stream but I couldn't reach a final conclusion about few options. I put bellow four approaches I would appreciatte if someone could give technical opnion if I understood correctly the real application based on very common requirements when dealing with soap client.
I am not loking for a simple answer like "Here I do successfully this way so you can copy and paste similar idea". I am really interested to understand if I am applying properly what I have read so far.
Firstly, my nested objects answered by web service:
//first level
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "OndernemingAlgemeenType", propOrder = {
"adressen",
... others properties
})
#XmlSeeAlso({
Onderneming20Type.class
})
public class OndernemingAlgemeenType
{
#XmlElement(name = "Adressen")
protected AdresOndernemingLijstType adressen;
... others elements
}
//second level that returns a list
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "AdresOndernemingLijstType", propOrder = {
"adres"
})
public class AdresOndernemingLijstType {
#XmlElement(name = "Adres", required = true)
protected List<AdresOndernemingType> adres;
...
}
// third level used to filter the list and return just one AdresOndernemingType
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "AdresOndernemingType", propOrder = {
"type"
})
public class AdresOndernemingType
extends AdresOndernemingBasisType{
#XmlElement(name = "Type", required = true)
protected TypeAdresOndernemingType type;
}
// fourth level
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "AdresOndernemingBasisType", propOrder = {
... not relevant for this question
})
#XmlSeeAlso({
AdresOndernemingType.class
})
public class AdresOndernemingBasisType
extends AdresBasisType
{
... not relevant for this question
}
// fifth level and finally, the desired fields (street and city)
#XmlAccessorType(XmlAccessType.FIELD)
#XmlType(name = "AdresBasisType", propOrder = {
"straat", //street
"gemeente" //city
})
#XmlSeeAlso({
AdresOndernemingDeelnemingInType.class,
AdresOndernemingBasisType.class
})
public class AdresBasisType {
#XmlElement(name = "Straat")
protected StraatRR20Type straat;
#XmlElement(name = "Gemeente")
protected GemeenteOptioneel20Type gemeente;
// Approaches with its understanding
Approach 1:
I understand that this is null exception safe.
I mean, in case either getAdressen or getAdres is null there will be no exception at all and no address printed.
private void printOptionalDidactic(){
Optional<AdresOndernemingLijstType> op = Optional.ofNullable(onderneming.getAdressen());
op.map(AdresOndernemingLijstType::getAdres).get().stream().filter(Objects::nonNull)
.filter(o -> "001".equals(o.getType().getCode().getValue().getValue()))
.map(x -> System.out.println("My street is: ".concat(x.getStraat())));
}
Approach 2:
Assuming that there will be never a null element (minOccurs="1" in xsd for all) so using Optional would be pointless
private void printNoOptionalDidactic(){
onderneming.getAdressen().getAdres().stream()
.filter(o -> "001".equals(o.getType().getCode().getValue().getValue()))
.map(x -> System.out.println("My street is: ".concat(x.getStraat())));
}
Approach 3:
Assuming I want to print all streets and I don't care about filtering, I understand there is no advantage at all to use flatMap before forEach
Replace nested loop with Java 8 flatmap
private void printForEachDidactic(){
onderneming.getAdressen().getAdres().stream()
.forEach(x -> System.out.println("My street is: ".concat(x.getStraat())));
}
Approach 4
Since no shared resource is used by the predicates and functions used in the process,
I understand I could use parallelism. Nevertheless, provided that it is little data so I have no real gain on it
Should I always use a parallel stream when possible?
private void printParallelDidactic(){
onderneming.getAdressen().getAdres().parallelStream()
.filter(o -> "001".equals(o.getType().getCode().getValue().getValue()))
.map(x -> System.out.println("My street is: ".concat(x.getStraat())));
}
I wouldn't use map stream method when you really do not map elements of the stream (e.g. in approach 1 .map(x -> System.out.println...). Better call forEach, it is dedicated to execute code for each element of the stream.
Regarding your first approach, I think there will be a NoSuchElementException thrown if onderneming.getAdressen() is null. See implementation of Optional.get:
public T get() {
if (value == null) {
throw new NoSuchElementException("No value present");
}
return value;
}
Otherwise all approaches look valid from the code point of view. Of course I cannot say anything about your data model.
However, you do not need to apply Java 8 streams and lambdas just because Java 8 introduced them. For example, I wouldn't use streams in your approach 3. An old school for loop will do it, too (and is slightly more readable here IMO):
for (AdresOndernemingType x : onderneming.getAdressen().getAdres()) {
System.out.println("My street is: ".concat(x.getStraat())));
}
Regarding parallel streams, I wouldn't use parallel() except when I really need parallelism to gain performance on huge data sets or long running tasks.

JAXB unmarshal map with object that contains yet another map

I have already use JAXB successfully to unmarshal certain xml file but I have another case that is more complex. Basically my xml file looks like these:
<BCLCurrenciesInflation>
<currenciesInflation>
<currency>
<curencyName>Yen</curencyName>
<countryName>Japan</countryName>
<issuingDate>1900-01-01</issuingDate>
<expirationDate></expirationDate>
<inflation>
<year>1900</year>
<percentage>16</percentage>
</inflation>
<inflation>
<year>1901</year>
<percentage>17</percentage>
</inflation>
</currency>
<currency>
<curencyName>Dolar</curencyName>
<countryName>USA</countryName>
<issuingDate>1900-01-01</issuingDate>
<expirationDate></expirationDate>
<inflation>
<year>1902</year>
<percentage>18</percentage>
</inflation>
<inflation>
<year>1903</year>
<percentage>19</percentage>
</inflation>
</currency>
</currenciesInflation>
so is basically a HashMap<String,BCLCurrency> and BCLCurrency looks like this:
public class BCLCurrency
{
#XmlElement(name = "currencyName")
public String name;
#XmlElement(name = "countryName")
public String country;
#XmlElement(name = "issuingDate")
public String issuingDate;
#XmlElement(name = "expirationDate")
public String expirationDate;
#XmlElement(name = "inflation")
public Map<String,float> inflationMap;
}
I was thinking about having two adapters, the second will be called from the first adapter but I don't feel comfortable with that solution.
Any ideas of how to pull this off?
PD: Funny story I cannot say hello at the beginning of the post, the system keeps suppressing it!
You can create/use a generic adaptor for map < K,V > instead of creating two adaptors.

Resources