StaxEventItemWriter XML Header - spring-batch

I am using StaxEventItemWriter for writing to an XML file. I am looking out to set standalone attribute on the xml tag to true. Is there any option to set it.
<?xml version="1.0" encoding="UTF-8" standalone="true"?>

You can extend StaxEventItemWriter and override the startDocument method. Here is an example:
#Bean
public StaxEventItemWriter<Person> itemWriter() {
StaxEventItemWriter<Person> itemWriter = new StaxEventItemWriter<Person>() {
#Override
protected void startDocument(XMLEventWriter writer) throws XMLStreamException {
writer.add(createXmlEventFactory().createStartDocument(DEFAULT_ENCODING, DEFAULT_XML_VERSION, true));
writer.add(xmlEventFactory.createStartElement(getRootTagNamespacePrefix(), getRootTagNamespace(), getRootTagName()));
}
};
// TODO set other properties on the writer
return itemWriter;
}

Related

FlatFileItemWriter not generating the file when using Tasklet approach

I wrote the following code using tasklet approach to generate a file with data.
public class PersonInfoFileWriter implements Tasklet {
#Autowired
PersonInfoFileUtil personInfoFileUtil;
public void write(ExecutionContext executionContext) throws IOException {
List<PersonInfo> personInfoList = null;
FlatFileItemWriter<PersonInfo> flatFileWriter = new FlatFileItemWriter<PersonInfo>();
flatFileWriter.setResource(new FileSystemResource("C:\\test\\"
+ LocalDate.now().format(DateTimeFormatter.BASIC_ISO_DATE) + ".txt"));
try {
flatFileWriter.open(executionContext);
String personName = (String) executionContext.get("personInfo");
//gets the details of the person by name from the database and assign the values to PersonInfo
personInfoList = personInfoFileUtil.setDataForPersonInfoFile(personName);
flatFileWriter.setName("Person-Detail-File");
flatFileWriter.setShouldDeleteIfEmpty(true);
flatFileWriter.setAppendAllowed(true);
flatFileWriter.setLineSeparator("\n");
flatFileWriter.setHeaderCallback(new FlatFileHeaderCallback() {
#Override
public void writeHeader(Writer writer) throws IOException {
writer.write(
"PersonId^Name^Program^ProgramType");
}
});
flatFileWriter.setLineAggregator(new DelimitedLineAggregator<PersonInfo>() {
{
setDelimiter("^");
setFieldExtractor((FieldExtractor<PersonInfo>) new BeanWrapperFieldExtractor<PersonInfo>() {
{
setNames(new String[] { "personId", "name", "program", "programType" });
}
});
}
});
String lines = flatFileWriter.doWrite((List<? extends PersonInfo>) personInfoList);
logger.info(lines); //this prints the information correctly
} finally {
flatFileWriter.close();
}
}
#Override
public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
ExecutionContext executionContext = contribution.getStepExecution().getJobExecution().getExecutionContext();
write(executionContext);
return RepeatStatus.FINISHED;
}
}
The above code compiles and runs without errors but it is not generating any file on to the disk.
I tried debugging to check if the fileName and etc values are getting created on to a buffer to write to a disk and everything works as intended except generating and writing the data to a file.
If I write the code using chunk based approach it is working fine.
Please let me know if I am doing any mistake. Thanks for the help in advance.
EDIT: after adding the changes that were suggested the file is getting created on the disk but the file is missing out the header that I have set using setHeaderCallback()
In your write method, you create an instance of FlatFileItemWriter, set some properties on it and then call close on it.
You did not call open() and write() methods, that's why it is not generating an file.

SOAP path resolution for dependent XSD-file not understood

I have two XSD-files, one is imported to the other. When retrieving the WSDL (via SoapUI) the imported xsd-file is not found.
Error loading [http://localhost:8294/authentication/shared-environment.xsd]:
org.apache.xmlbeans.XmlException: org.apache.xmlbeans.XmlException: error:
Unexpected end of file after null
The two of the xsd-files reside in the same folder:
src/main/resources
- auth-attributes.xsd
- shared-environment.xsd
The "auth-attributes.xsd" looks like this:
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
targetNamespace="http://dto.shared.auth.appl.com"
xmlns:Q1="http://dto.shared.auth.appl.com"
xmlns:Q3="http://dto.common.appl.com" elementFormDefault="qualified">
<xs:import namespace="http://dto.common.appl.com"
schemaLocation="shared-environment.xsd" />
.........
.........
.........
The WS-Adapter is defined this way:
#EnableWs
#Configuration
public class BackendServerConfig extends WsConfigurerAdapter {
#Bean
ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<MessageDispatcherServlet>(servlet, "/authentication/*");
}
#Bean(name = "authentication")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema authenticationSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("SharePort");
wsdl11Definition.setLocationUri("/authentication");
wsdl11Definition.setTargetNamespace("http://dto.shared.auth.timetracker.appl.com");
wsdl11Definition.setSchema(authenticationSchema);
return wsdl11Definition;
}
#Bean
public XsdSchema authenticationSchema() {
return new SimpleXsdSchema(new ClassPathResource("auth-attributes.xsd"));
}
I'm not really familiar with WSDL. The JAXB source generation from the XSDs is fine but the WSDL resolution fails. I't seems I need a method to tell the WSDL-building mechanism where to retrieve the imported XSDs.
The "DefaultWsdl11Definition" has got the method setSchemaCollection() to register multiple XSD-definitions. I used this method instead of setSchema - this helped - at least a bit. The error message has gone and the Soap-requests are answered correctly.
Only, when "adding" a new project to SoapUI, the client does not create automatically requests - for whatever reason - this only works with "import" from webserver.
The WS-Configuration now looks like this:
#EnableWs
#Configuration
public class BackendServerConfig extends WsConfigurerAdapter {
#Bean
ServletWebServerFactory servletWebServerFactory(){
return new TomcatServletWebServerFactory();
}
#Bean
public ServletRegistrationBean<MessageDispatcherServlet> messageDispatcherServlet(ApplicationContext applicationContext) {
MessageDispatcherServlet servlet = new MessageDispatcherServlet();
servlet.setApplicationContext(applicationContext);
servlet.setTransformWsdlLocations(true);
return new ServletRegistrationBean<MessageDispatcherServlet>(servlet, "/authentication/*");
}
#Bean(name = "authentication")
public DefaultWsdl11Definition defaultWsdl11Definition(XsdSchema authenticationSchema) {
DefaultWsdl11Definition wsdl11Definition = new DefaultWsdl11Definition();
wsdl11Definition.setPortTypeName("SharePort");
wsdl11Definition.setLocationUri("/authentication");
wsdl11Definition.setTargetNamespace("http://dto.shared.auth.app.com");
wsdl11Definition.setSchemaCollection(schemaCollection());
return wsdl11Definition;
}
private XsdSchemaCollection schemaCollection() {
CommonsXsdSchemaCollection commonsXsdSchemaCollection = new CommonsXsdSchemaCollection(
new ClassPathResource("auth-attributes.xsd"),
new ClassPathResource("shared-environment.xsd"));
commonsXsdSchemaCollection.setInline(true);
return commonsXsdSchemaCollection;
}

Spring boot REST API Returning a List/Array Formatting issue

I am developing spring boot based web services API. I need to return a list of things (ProductData) for the GET response.
This is what the response looks like
<ProductDataList>
<ProductData>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
</ProductData>
</ProductDataList>
But I don't need the extra <ProductData> tag.
I need the response as below.
<ProductDataList>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
</ProductDataList>
Any idea why an extra tag is generated?
I have below in my WebMvcConfig file.
#Bean
public MappingJackson2XmlHttpMessageConverter xmlConverter() {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.propertyNamingStrategy(PropertyNamingStrategy.
PascalCaseStrategy.PASCAL_CASE_TO_CAMEL_CASE);
builder.serializationInclusion(JsonInclude.Include.NON_EMPTY);
builder.failOnUnknownProperties(false);
MappingJackson2XmlHttpMessageConverter xmlConverter =
new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build());
return xmlConverter;
}
In my controller I have
#RequestMapping(value = "/productdata")
#ResponseBody
public ProductDataList getProductData(#RequestParam final String[] ids) {
ArrayList<ProductData> products = productDataService.getProductData(ids);
ProductData[] pdArray = new ProductData[products.size()];
products.toArray(pdArray);
ProductDataList productDataList = new ProductDataList();
productDataList.setProductData(pdArray);
return productDataList;
}
This is my ProductDataList class.
public class ProductDataList{
ProductData[] productData;
public ProductData[] getProductData() {
return productData;
}
public void setProductData(ProductData[] productData) {
this.productData = productData;
}
}
Edit 1.
When I return ArrayList<ProductData> the response was like this.
<ArrayList>
<item>...</item>
<item>...</item>
<item>...</item>
</ArrayList>
Edit 2.
After adding annotation JsonTypeInfo I made some progress, but not quite there to what I wanted.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY)
public class ProductData {}
<ProductDataList>
<item _type="ProductData">...</item>
<item _type="ProductData">...</item>
<item _type="ProductData">...</item>
<ProductDataList>
Objects aren't built that way. It's doing exactly as you're asking:
<ProductDataList> <!-- the ProductDataList object -->
<ProductData> <!-- the property containing the array -->
<ProductData>...</ProductData> <!-- each ProductData object -->
<ProductData>...</ProductData>
<ProductData>...</ProductData>
</ProductData>
</ProductDataList>
This is to ensure that other properties on the ProductDataList object would also have a spot inside the tags, e.g.
<ProductDataList>
<MyArray>...</MyArray>
<MyProperty></MyProperty>
</ProductDataList>
To get around this, you might as well try to cut out the Object middle-man.
#RequestMapping(value = "/productdata")
#ResponseBody
public ArrayList<ProductData> getProductDataList(#RequestParam final String[] ids) {
return productDataService.getProductData(ids);
}
If it works at all (I seem to remember JAXB not being able to parse ArrayLists), your ObjectMapper will give you...
<ArrayList>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
</ArrayList>
...instead of the root tag that you're hoping for. But if it DOES work, then just create a class that extends ArrayList and doesn't do anything, then return it instead.
public class ProductDataList<E> extends ArrayList<E>{ ... }
And then...
#RequestMapping(value = "/productdata")
#ResponseBody
public ProductDataList<ProductData> getProductDataList(#RequestParam final String[] ids) {
return (ProductDataList) productDataService.getProductData(ids);
}
Happy hunting.
Kieron
After some effort I was able to get this resolved. Key thing is to have #JacksonXmlElementWrapper(useWrapping = false) in the Object as mentioned in this answer
#JacksonXmlRootElement(localName = "ProductDataList")
public class ProductDataList {
#JacksonXmlProperty(localName = "ProductData")
#JacksonXmlElementWrapper(useWrapping = false)
ProductData[] productDataArray = null;
public ProductData[] getProductDataArray() {
return productDataArray;
}
public void setProductDataArray(ProductData[] productDataArray) {
this.productDataArray = productDataArray;
}
}
Now the response looks like just as I wanted to be.
<ProductDataList>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
<ProductData>...</ProductData>
</ProductDataList>

How to POST InputStream as the body of a request in Retrofit?

I'm attempting to do a POST with the body being an InputStream with something like this:
#POST("/build")
#Headers("Content-Type: application/tar")
Response build(#Query("t") String tag,
#Query("q") boolean quiet,
#Query("nocache") boolean nocache,
#Body TypedInput inputStream);
In this case the InputStream is from a compressed tar file.
What's the proper way to POST an InputStream?
You can upload inputStream using Multipart.
#Multipart
#POST("pictures")
suspend fun uploadPicture(
#Part part: MultipartBody.Part
): NetworkPicture
Then in perhaps your repository class:
suspend fun upload(inputStream: InputStream) {
val part = MultipartBody.Part.createFormData(
"pic", "myPic", RequestBody.create(
MediaType.parse("image/*"),
inputStream.readBytes()
)
)
uploadPicture(part)
}
If you want to find out how to get an image Uri, check this answer: https://stackoverflow.com/a/61592000/10030693
TypedInput is a wrapper around an InputStream that has metadata such as length and content type which is used in making the request. All you need to do is provide a class that implements TypedInput which passed your input stream.
class TarFileInput implements TypedInput {
#Override public InputStream in() {
return /*your input stream here*/;
}
// other methods...
}
Be sure you pass the appropriate return values for length() and mimeType() based on the type of file from which you are streaming content.
You can also optionally pass it as an anonymous implementation when you are calling your build method.
The only solution I came up with here was to use the TypeFile class:
TypedFile tarTypeFile = new TypedFile("application/tar", myFile);
and the interface (without explicitly setting the Content-Type header this time):
#POST("/build")
Response build(#Query("t") String tag,
#Query("q") boolean quiet,
#Query("nocache") boolean nocache,
#Body TypedInput inputStream);
Using my own implementation of TypedInput resulted in a vague EOF exception even while I provided the length().
public class TarArchive implements TypedInput {
private File file;
public TarArchive(File file) {
this.file = file;
}
public String mimeType() {
return "application/tar";
}
public long length() {
return this.file.length();
}
public InputStream in() throws IOException {
return new FileInputStream(this.file);
}
}
Also, while troubleshooting this issue I tried using the latest Apache Http client instead of OkHttp which resulted in a "Content-Length header already present" error even though I wasn't explicitly setting that header.
According to the Multipart section of http://square.github.io/retrofit/ you'll want to use TypedOutput instead of TypedInput. Following their examples for multipart uploads worked fine for me once I had implemented a TypedOutput class.
My solution was to implement TypedOutput
public class TypedStream implements TypedOutput{
private Uri uri;
public TypedStream(Uri uri){
this.uri = uri;
}
#Override
public String fileName() {
return null;
}
#Override
public String mimeType() {
return getContentResolver().getType(uri);
}
#Override
public long length() {
return -1;
}
#Override
public void writeTo(OutputStream out) throws IOException {
Utils.copyStream(getContentResolver().openInputStream(uri), out);
}
}

GWT FileUpload - Servlet options and handling response

I am new to GWT and am trying to implement a file upload functionality.
Found some implementation help over the internet and used that as reference.
But have some questions related to that:
The actual upload or writing the contents of file on server(or disk) will be done by a servlet.
Is it necessary that this servlet (say MyFileUploadServlet) extends HttpServlet? OR
I can use RemoteServiceServlet or implement any other interface? If yes, which method do I need to implement/override?
In my servlet, after everything is done, I need to return back the response back to the client.
I think form.addSubmitCompleteHandler() can be used to achieve that. From servlet, I could return text/html (or String type object) and then use SubmitCompleteEvent.getResults() to get the result.
Question is that can I use my custom object instead of String (lets say MyFileUploadResult), populate the results in it and then pass it back to client?
or can I get back JSON object?
Currently, after getting back the response and using SubmitCompleteEvent.getResults(), I am getting some HTML tags added to the actual response such as :
pre> Image upload successfully /pre> .
Is there a way to get rid of that?
Thanks a lot in advance!
Regards,
Ashish
To upload files, I have extended HttpServlet in the past. I used it together with Commons-FileUpload.
I made a general widget for form-based uploads. That was to accommodate uploads for different file types (plain text and Base64). If you just need to upload plain text files, you could combine the following two classes into one.
public class UploadFile extends Composite {
#UiField FormPanel uploadForm;
#UiField FileUpload fileUpload;
#UiField Button uploadButton;
interface Binder extends UiBinder<Widget, UploadFile> {}
public UploadFile() {
initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
fileUpload.setName("fileUpload");
uploadForm.setEncoding(FormPanel.ENCODING_MULTIPART);
uploadForm.setMethod(FormPanel.METHOD_POST);
uploadForm.addSubmitHandler(new SubmitHandler() {
#Override
public void onSubmit(SubmitEvent event) {
if ("".equals(fileUpload.getFilename())) {
Window.alert("No file selected");
event.cancel();
}
}
});
uploadButton.addClickHandler(new ClickHandler() {
#Override
public void onClick(ClickEvent event) {
uploadForm.submit();
}
});
}
public HandlerRegistration addCompletedCallback(
final AsyncCallback<String> callback) {
return uploadForm.addSubmitCompleteHandler(new SubmitCompleteHandler() {
#Override
public void onSubmitComplete(SubmitCompleteEvent event) {
callback.onSuccess(event.getResults());
}
});
}
}
The UiBinder part is pretty straighforward.
<g:HTMLPanel>
<g:HorizontalPanel>
<g:FormPanel ui:field="uploadForm">
<g:FileUpload ui:field="fileUpload"></g:FileUpload>
</g:FormPanel>
<g:Button ui:field="uploadButton">Upload File</g:Button>
</g:HorizontalPanel>
</g:HTMLPanel>
Now you can extend this class for plain text files. Just make sure your web.xml serves the HttpServlet at /textupload.
public class UploadFileAsText extends UploadFile {
public UploadFileAsText() {
uploadForm.setAction(GWT.getModuleBaseURL() + "textupload");
}
}
The servlet for plain text files goes on the server side. It returns the contents of the uploaded file to the client. Make sure to install the jar for FileUpload from Apache Commons somewhere on your classpath.
public class TextFileUploadServiceImpl extends HttpServlet {
private static final long serialVersionUID = 1L;
#Override
protected void doPost(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
if (! ServletFileUpload.isMultipartContent(request)) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST,
"Not a multipart request");
return;
}
ServletFileUpload upload = new ServletFileUpload(); // from Commons
try {
FileItemIterator iter = upload.getItemIterator(request);
if (iter.hasNext()) {
FileItemStream fileItem = iter.next();
// String name = fileItem.getFieldName(); // file name, if you need it
ServletOutputStream out = response.getOutputStream();
response.setBufferSize(32768);
int bufSize = response.getBufferSize();
byte[] buffer = new byte[bufSize];
InputStream in = fileItem.openStream();
BufferedInputStream bis = new BufferedInputStream(in, bufSize);
long length = 0;
int bytes;
while ((bytes = bis.read(buffer, 0, bufSize)) >= 0) {
out.write(buffer, 0, bytes);
length += bytes;
}
response.setContentType("text/html");
response.setContentLength(
(length > 0 && length <= Integer.MAX_VALUE) ? (int) length : 0);
bis.close();
in.close();
out.flush();
out.close();
}
} catch(Exception caught) {
throw new RuntimeException(caught);
}
}
}
I cannot recall how I got around the <pre></pre> tag problem. You may have to filter the tags on the client. The topic is also addressed here.