Spring Batch FlatFileItemReader populating field value across all items/lines read - java

I am reading a flat file using spring batch FlatFileItemReader.
I have a requestId field which i need to populate with a unique value for all records read from the flat file.
eg: When i read file1. I want to set the requestId to 1 for all Item objects created at requestId field. For file2, i need to set requestId to 2.
my requestId is uniquely generated by a separate class.
How can I achieve this using spring batch?

there are some possible solutions
use an ResourceAware Item
public class MyItem implements ResourceAware {
private Resource resource;
public String getId() {
return createIdFromResource(resource);
}
private String createIdFromResource(final Resource resource) {
// create your ID here
return resource.getFilename();
}
#Override
public void setResource(final Resource resource) {
this.resource = resource;
}
}
use an Listener (here with interfaces, less verbose use of annotations is possible too)
public class TestListener implements StepExecutionListener, ItemReadListener<String> {
private StepExecution stepExecution;
private static final String CURRENT_ID = "currentId";
#Override
public void beforeStep(final StepExecution stepExecution) {
this.stepExecution = stepExecution;
}
#Override
public ExitStatus afterStep(final StepExecution stepExecution) {
return null;
}
#Override
public void beforeRead() {
}
#Override
public void afterRead(final String item) {
String currentId = null;
if (stepExecution.getExecutionContext().containsKey(CURRENT_ID)) {
currentId = stepExecution.getExecutionContext().getString(CURRENT_ID);
} else {
String fileName = stepExecution.getExecutionContext().getString("fileName");
// ... create ID from FileName
currentId = fileName + "foo";
stepExecution.getExecutionContext().put(CURRENT_ID, currentId);
}
}
#Override
public void onReadError(final Exception ex) {
}
}
in the above example the current fileName is avavailable in the stepExecutionContext, it might be you have to pull it from jobParameters and extract the filename
String paramValue = stepExecution.getJobExecution().getJobParameters().getString("paramName");
// extract fileName from paramValue

Related

JUnit/Mockito: How to mock or create a private member variable

I have a private String variable filePath that will be set in the SpringBoot's execute(..) method and then the value will be used in another method that will be called from inside this execute(..).
#Component("filebatchjobtask")
public class FileBatchJobTask extends BaseFileBatchJobTask implements Tasklet {
private String filePath; // PRIVATE VARIABLE THAT WILL BE USED IN A CALL
private static final CalLogger LOGGER = CalLoggerFactory.getLogger(FileBatchJobTask.class);
#Override
public RepeatStatus execute(final StepContribution stepContribution, final ChunkContext chunkContext) throws Exception {
// INITIALIZE PRIVATE VARIABLE HERE
filePath = chunkContext.getStepContext().getJobParameters().get(Constants.FILEPATH).toString();
processeFile(); // METHOD CALL WHERE FILEPATH INITIALIZED ABOVE WILL BE USED
return RepeatStatus.FINISHED;
}
#Override
protected void processeFile() throws IOException {
LOGGER.warn("FileBatchJobTask:processeFile():: Directory to process files: " + filePath);
File[] filelist = geteFiles(filePath); // THIS IS THE CALL I WANT TO MOCK
if (filelist == null || filelist.length < 1) {
LOGGER.warn("FileBatchJobTask: No eFiles available to process");
return;
}
LOGGER.warn("Total number of files to process: " + filelist.length);
}
It's corresponding test below:
//#RunWith(PowerMockRunner.class)
#RunWith(MockitoJUnitRunner.class)
public class FileBatchJobTaskTest extends BaseFileBatchJobTaskTest {
#InjectMocks
FileBatchJobTask fileBatchJobTask;
#Override
BaseFileBatchJobTask createFileBatchJobTask() {
return fileBatchJobTask;
}
#Test
public void processeFile() {
BaseFileBatchJobTask batchJobTask = Mockito.spy(createFileBatchJobTask());
// THIS resourceDir is the I want to use instead of filePath variable in tests here and pick file from this test resource path
Path resourceDir = Paths.get("src", "test", "resources", "data", "validation");
resourcePath = resourceDir.toFile().getAbsolutePath();
File fileDir = new File(resourcePath);
File[] files = fileDir.listFiles(new FileFilter() {
#Override
public boolean accept(final File pathname) {
String name = pathname.getName().toLowerCase();
return name.endsWith(".xml") && pathname.isFile();
}
});
doReturn(files).when(batchJobTask).geteFiles(anyString()); // THIS IS THE CALL I AM TRYING TO MOCK
try {
fileBatchJobTask.processeFile();
Assert.assertTrue(true);
} catch (...) {
}
}
This is the base class
class BaseFileBatchJobTask {
protected File[] geteFiles(final String eFileDirPath) {
File fileDir = new File(eFileDirPath); // NPE as eFileDirPath is null
File[] files = fileDir.listFiles(new FileFilter() {
#Override
public boolean accept(final File pathname) {
String name = pathname.getName().toLowerCase();
return name.endsWith(".xml") && pathname.isFile();
}
});
return files;
}
}
ERROR: I am getting NPE as when the test is run, getEFiles() is executed and filePath is null. Since I am mocking, it shouldn't go inside the actual implementation of the method. However, seems it's not being mocked as expected, so need help in figuring out the issue.
Also looked up a lot of SO posts but couldn't figure out the issue so please don't mark as duplicate if you don't know the answer :)
You need to call processeFile() on the spied version of your jobTask, not on the original one. Think about a spy being a wrapper around the spied object, that intercepts the mocked calls.
For short, just use batchJobTask inside the try-catch block like this:
try {
batchJobTask.processeFile();
Assert.assertTrue(true);
} catch (...) {
}

How to create a domain object from a Json element?

the external web service returns me a Json file of the form
{"forecasts":[{"period_end":"2021-01-15T01:00:00.0000000Z","period":"PT30M","ghi90":0,"ghi":0,"ghi10":0},{"period_end":"2021-01-15T01:30:00.0000000Z","period":"PT30M","ghi90":0,"ghi":0,"ghi10":0},{"period_end":"2021-01-15T02:00:00.0000000Z","period":"PT30M","ghi90":0,"ghi":0,"ghi10":0}]}
Using RestRespone a transform an json element
RestResponse resp = rest.get(url)
resp.json instanceof JsonElement
How can I create a domain object from the Json element variable, knowing that my wrapper class is
class ForecastGhi {
static constraints = {
}
private ArrayList<IrradianciaGlobalHorizontal> forecast
ArrayList<IrradianciaGlobalHorizontal> getForecast() {
return forecast
}
void setForecast(ArrayList<IrradianciaGlobalHorizontal> forecast) {
this.forecast = forecast
}
}
and de persist domain class is
class IrradianciaGlobalHorizontal {
static constraints = {
}
#JsonProperty("all")
private def period_end
private def period
private def ghi90
private def ghi
private def ghi10
def getGhi() {
this.ghi
}
void setGhi(int ghi) {
this.ghi = ghi
}
def getGhi90() {
this.ghi90
}
void setGhi90(int ghi90) {
this.ghi90 = ghi90
}
def getGhi10() {
this.ghi10
}
void setGhi10(int ghi10) {
this.ghi10 = ghi10
}
def getPeriod_end() {
this.period_end
}
void setPeriod_end(Date period_end) {
this.period_end = period_end
}
def getPeriod() {
this.period
}
void setPeriod(String period) {
this.period = period
}
}
Help please; thanks a lot
This is an issue with your API implementation; The endpoint changed the domain field names &/or domain name. This will cause issues with bringing said data back in.
Either that or front-end is not matching the API docs for the endpoint.
Field names/domain names should match the domain/resource unless you are going for a level of obfuscation and then accept that you are going to need a middle layer to act as a translater (ie EDI).
You want output to be able to be read as input by the same endpoint by merely changing the request method.
My suggestion (easiest solution): change original endpoint to match domain/resource field names
If you have the opportunity to use Jackson library, you can do this:
ForecastGhi request = objectMapper.readValue(jsonAsText, ForecastGhi.class);
Create an objectMapper and configure to fail in case of unknown properties (just in case)
private String getJsonAsTextFromRest() {
String message = " {\"forecasts\":[{\"period_end\":\"2021-01-15T01:00:00.0000000Z\",\"period\":\"PT30M\",\"ghi90\":0,\"ghi\":0,\"ghi10\":0},{\"period_end\":\"2021-01-15T01:30:00.0000000Z\",\"period\":\"PT31M\",\"ghi90\":0,\"ghi\":0,\"ghi10\":0},{\"period_end\":\"2021-01-15T02:00:00.0000000Z\",\"period\":\"PT32M\",\"ghi90\":0,\"ghi\":0,\"ghi10\":0}]}";
return message;
}
#Override
public void run(String... arg0) throws Exception {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
String jsonAsText = getJsonAsTextFromRest();
ForecastGhi request = objectMapper.readValue(jsonAsText, ForecastGhi.class);
request.getForecast().stream().forEach(it -> System.out.println(it.getPeriod() + " " + it.getGhi()));
}
public class IrradianciaGlobalHorizontal {
private Date period_end;
private String period;
private int ghi90;
private int ghi;
private int ghi10;
public int getGhi() {
return this.ghi;
}
public void setGhi(int ghi) {
this.ghi = ghi;
}
public int getGhi90() {
return this.ghi90;
}
public void setGhi90(int ghi90) {
this.ghi90 = ghi90;
}
public int getGhi10() {
return this.ghi10;
}
void setGhi10(int ghi10) {
this.ghi10 = ghi10;
}
public Date getPeriod_end() {
return this.period_end;
}
public void setPeriod_end(Date period_end) {
this.period_end = period_end;
}
public String getPeriod() {
return this.period;
}
public void setPeriod(String period) {
this.period = period;
}
}
ForecastGhi class.
import com.fasterxml.jackson.annotation.JsonProperty;
public class ForecastGhi {
private ArrayList<IrradianciaGlobalHorizontal> forecast;
#JsonProperty("forecasts")//It must be the same as the json property
public ArrayList<IrradianciaGlobalHorizontal> getForecast() {
return forecast;
}
#JsonProperty("forecasts")
public void setForecast(ArrayList<IrradianciaGlobalHorizontal> forecast) {
this.forecast = forecast;
}
}
Results:
PT30M 0
PT31M 0
PT32M 0
Dependencies Gradle:
compile group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.12.1'
Or
Dependencies Maven:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.12.1</version>
</dependency>
Note: in your json example you use forecasts, but your java property name is forecast. In that case its necessary to decorate the property with #JsonProperty("forecasts"). If you dont do it, you get an error like this com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException: Unrecognized field "forecasts"

Unable to read list of user defined class from application.yml file in a Java Spring Boot project

Hello Team,
I recently tried reading contents from application.yml file in a Spring Boot project (Version 2.3.4).
Initially, all the properties from yml file were getting read as null.
After cleaning and rebuilding project several times, I could read all the properties except the List of user defined class object (List<LogComponents> in below class) which is still getting read as null.
I tried all the possible solutions but nothing worked for me.
Could you please check and help me in understanding what I have missed in below code because of which the value for List<LogComponent> logComponents is still getting read as null from yml file?
Thanking you in anticipation!
Configuration Java Class
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties
public class TestAPIConfiguration {
private String eventCache;
private String diskBasedCache;
private List<String> sendAllSMSto;
private List<String> sendAllEmailsto;
//This property is getting read as null even if
//value for this property is present in yml file.
private List<LogComponent> logComponents;
#NotNull
private String selfURIPrefix;
#NotNull
private String investURIPrefix;
#NotNull
private String ifaURIPrefix;
private String apiEnv;
private final Joiner joiner = Joiner.on( "," ).skipNulls();
private static final Logger LOGGER = LoggerFactory.getLogger(TestAPIConfiguration.class);
#PostConstruct
public void setSystemProperties()
{
try
{
System.setProperty(SystemConstants.EVENT_CACHE_PATH, eventCache);
System.setProperty(SystemConstants.DISK_BASED_CACHE_PATH, diskBasedCache);
System.setProperty(SystemConstants.REQUEST_LOGGING_FIELDS,
JSONUtils.getObjectMapper().writeValueAsString(logComponents));
System.setProperty(SystemConstants.ENVIRONMENT_IDENTIFIER, apiEnv);
System.setProperty(INVEST_URI_PREFIX, investURIPrefix);
System.setProperty(IFA_URI_PREFIX, ifaURIPrefix);
if(sendAllSMSto != null)
System.setProperty(SEND_ALL_SMS_TO, joiner.join(sendAllSMSto));
if(sendAllEmailsto != null)
System.setProperty(SystemConstants.SEND_ALL_EMAILS_TO, joiner.join(sendAllEmailsto));
}
catch(Exception se)
{
LOGGER.error("Error in Configuration Setup: {}", se.getLocalizedMessage());
}
}
public String getEventCache() {
return eventCache;
}
public void setEventCache(String eventCache) {
this.eventCache = eventCache;
}
public String getDiskBasedCache() {
return diskBasedCache;
}
public void setDiskBasedCache(String diskBasedCache) {
this.diskBasedCache = diskBasedCache;
}
public List getSendAllSMSto() {
return sendAllSMSto;
}
public void setSendAllSMSto(List<String> sendAllSMSto) {
this.sendAllSMSto = sendAllSMSto;
}
public List getSendAllEmailsto() {
return sendAllEmailsto;
}
public void setSendAllEmailsto(List<String> sendAllEmailsto) {
this.sendAllEmailsto = sendAllEmailsto;
}
public List getRequestLoggingFields() {
return logComponents;
}
public void setRequestLoggingFields(List<LogComponent> requestLoggingFields) {
this.logComponents = requestLoggingFields;
}
public String getSelfURIPrefix() {
return selfURIPrefix;
}
public void setSelfURIPrefix(String selfURIPrefix) {
this.selfURIPrefix = selfURIPrefix;
}
public String getInvestURIPrefix() {
return investURIPrefix;
}
public void setInvestURIPrefix(String investURIPrefix) {
this.investURIPrefix = investURIPrefix;
}
public String getIfaURIPrefix() {
return ifaURIPrefix;
}
public void setIfaURIPrefix(String ifaURIPrefix) {
this.ifaURIPrefix = ifaURIPrefix;
}
public String getApiEnv() {
return apiEnv;
}
public void setApiEnv(String apiEnv) {
this.apiEnv = apiEnv;
}
}
LogComponent Java Class
#Component
public class LogComponent {
#NotNull
private String headerName;
#NotNull
private String sessionKey;
#NotNull
private String logPrintKey;
public String getHeaderName() {
return headerName;
}
public String getSessionKey() {
return sessionKey;
}
public String getLogPrintKey() {
return logPrintKey;
}
}
application.yml File
debug: true
server:
port: 8080
apiEnv: UAT
selfURIPrefix: "https://testurl.localhost.net"
investURIPrefix: "https://testurl.mediaserver.net"
ifaURIPrefix: "https://testurl.secondaryserver.net"
sendAllSMSto:
- "0000000000"
sendAllEmailsto:
- "abc#testmail.com"
eventCache: "C:\\Users\\username\\project\\devnull\\eventcachepurchase.mdb"
diskBasedCache: "C:\\Users\\username\\project\\devnull\\cache.mdb"
logComponents:
- headerName: X-RT-REQUEST-TRACKER
sessionKey: NOT AVAILABLE
logPrintKey: REQUEST-TRACKER
- headerName: X-RT-INX-DWD
sessionKey: IFX-PDR
logPrintKey: PDR_NO
- headerName: X-RT-IFA-ARN
sessionKey: IRX-AXRN
logPrintKey: AXR-CDODEEE
Finally, I found the solution.
I had not created setter methods inside the LogComponent class because of which the values were not getting assigned to the variables.
After adding the setters for all the fields, this issue has been resolved.

how to use path variable in californium CoAP server?

Similar with Restful syntax in Jersey or other framework, I could fetch the variable in the Restful uri path like that:
#Path("/users/{username}")
public class UserResource {
#GET
#Produces("text/xml")
public String getUser(#PathParam("username") String userName) {
...
}
}
but in californium, the syntax is different, I try these codes but it is not correct:
class usersextends CoapResource {
public users() {
super("users/{username}");
}
#Override
public void handleGET(CoapExchange exchange) {
exchange.respond("The username is "+ ???????);
}
}
How could I use the same function as first piece of code did? Another thing is where I can find official document introduce the API? I just saw the source code and try to find the solution now.
Create your own MessageDeliverer and change findResource method:
public class MyMessageDeliverer implements MessageDeliverer {
private final Resource root;
public MyMessageDeliverer(Resource root) {
this.root = root;
}
/* You can use implementation of methods from ServerMessageDeliverer */
#Override
public void deliverRequest(Exchange exchange) {
}
#Override
public void deliverResponse(Exchange exchange, Response response) {
}
/* method returns last known Resource instead of null*/
private Resource findResource(List<String> list) {
LinkedList<String> path = new LinkedList<String>(list);
Resource current = root;
Resource last = null;
while (!path.isEmpty() && current != null) {
last = current;
String name = path.removeFirst();
current = current.getChild(name);
}
if (current == null) {
return last;
}
return current;
}
}
Use your MessageDeliverer:
server = new CoapServer();
server.setMessageDeliverer(new MyMessageDeliverer(server.getRoot()));
Add your Resource to server:
server.add(new Users());
Request /users/{username} will be delivered to your Users resource. Fetch the variable from request URI:
public class Users extends CoapResource {
public Users() {
super("users");
}
public void handleGet(CoapExchange exchange) {
List<String> uri = exchange.getRequestOptions().getUriPath();
uri.remove("users");
String username = uri.remove(0);
//for query params:
Map<String, String> params = new HashMap<String, String>();
for (String p : exchange.getRequestOptions().getUriQuery()) {
String[] parts = p.split("=");
params.put(parts[0], parts[1]);
}
String param = params.get("param");
}
}

Spring Boot batch - MultiResourceItemReader : move to next file on error

In a batch service, I read multiple XML files using a MultiResourceItemReader, which delegate to a StaxEventItemReader.
If an error is raised reading a file (a parsing exception for example), I would like to specify to Spring to start reading the next matching file. Using #OnReadError annotation and/or a SkipPolicy for example.
Currently, when a reading exception is raised, the batch stops.
Does anyone have an idea how to do it ?
EDIT: I see MultiResourceItemReader has a method readNextItem(), but it's private -_-
I'm not using SB for a while, but looking MultiResourceItemReader code I suppose you can write your own ResourceAwareItemReaderItemStream wrapper where you check for a flag setted to move to next file or to perform a standard read using a delegate.
This flag can be stored into execution-context or into your wrapper and should be cleared after a move next.
class MoveNextReader<T> implements ResourceAwareItemReaderItemStream<T> {
private ResourceAwareItemReaderItemStream delegate;
private boolean skipThisFile = false;
public void setSkipThisFile(boolean value) {
skipThisFile = value;
}
public void setResource(Resource resource) {
skipThisFile = false;
delegate.setResource(resource);
}
public T read() {
if(skipThisFile) {
skipThisFile = false;
// This force MultiResourceItemReader to move to next resource
return null;
}
return delegate.read();
}
}
Use this class as delegate for MultiResourceItemReader and in #OnReadErrorinject MoveNextReader and set MoveNextReader.skipThisFile.
I can't test code from myself but I hope this can be a good starting point.
Here are my final classes to read multiple XML files and jump to the next file when a read error occurs on one (thanks to Luca's idea).
My custom ItemReader, extended from MultiResourceItemReader :
public class MyItemReader extends MultiResourceItemReader<InputElement> {
private SkippableResourceItemReader<InputElement> reader;
public MyItemReader() throws IOException {
super();
// Resources
PathMatchingResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
this.setResources( resourceResolver.getResources( "classpath:input/inputFile*.xml" ) );
// Delegate reader
reader = new SkippableResourceItemReader<InputElement>();
StaxEventItemReader<InputElement> delegateReader = new StaxEventItemReader<InputElement>();
delegateReader.setFragmentRootElementName("inputElement");
Jaxb2Marshaller unmarshaller = new Jaxb2Marshaller();
unmarshaller.setClassesToBeBound( InputElement.class );
delegateReader.setUnmarshaller( unmarshaller );
reader.setDelegate( delegateReader );
this.setDelegate( reader );
}
[...]
#OnReadError
public void onReadError( Exception exception ){
reader.setSkipResource( true );
}
}
And the ItemReader-in-the-middle used to skip the current resource :
public class SkippableResourceItemReader<T> implements ResourceAwareItemReaderItemStream<T> {
private ResourceAwareItemReaderItemStream<T> delegate;
private boolean skipResource = false;
#Override
public void close() throws ItemStreamException {
delegate.close();
}
#Override
public T read() throws UnexpectedInputException, ParseException, NonTransientResourceException, Exception {
if( skipResource ){
skipResource = false;
return null;
}
return delegate.read();
}
#Override
public void setResource( Resource resource ) {
skipResource = false;
delegate.setResource( resource );
}
#Override
public void open( ExecutionContext executionContext ) throws ItemStreamException {
delegate.open( executionContext );
}
#Override
public void update( ExecutionContext executionContext ) throws ItemStreamException {
delegate.update( executionContext );
}
public void setDelegate(ResourceAwareItemReaderItemStream<T> delegate) {
this.delegate = delegate;
}
public void setSkipResource( boolean skipResource ) {
this.skipResource = skipResource;
}
}

Categories

Resources