Spring 5: use RestTemplate to POST multiple files with POJO; MediaType.MULTIPART_FORM_DATA with LinkedMultiValueMap - resttemplate

Versions:
Java 11
Spring 5.3.9
Jackson 2.13
org.apache.httpcomponents:httpclient: 4.5.13
I believe the issue here is that out-of-the-box, RestTemplate cannot deal with an HttpEntity for which the value is itself a MultiValueMap with (String, Resource) pairs. How to resolve that? I suppose the canonical use-case is supporting the upload of multiple files concurrently through an HTML form, along with meta-data. Details follow.
Here are message converters:
private List<HttpMessageConverter<?>> getMessageConverters()
{
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_HTML);
mediaTypes.add(MediaType.APPLICATION_JSON);
mediaTypes.add(MediaType.TEXT_PLAIN);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(mediaTypes);
List<MediaType> formMediaTypes = new ArrayList<>();
formMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
formMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
formConverter.setSupportedMediaTypes(formMediaTypes);
formConverter.addPartConverter(new MappingJackson2HttpMessageConverter());
formConverter.addPartConverter(new ResourceHttpMessageConverter());
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setSupportedMediaTypes(formMediaTypes);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(converter);
messageConverters.add(formConverter);
messageConverters.add(stringConverter);
return messageConverters;
}
And:
RestTemplate restTemplate = new RestTemplate(requestFactory);
restTemplate.setMessageConverters(getMessageConverters());
Then I create one HttpEntity with the files (in this example, I am only POSTing a single file):
ByteArrayResource bas = new ByteArrayResource(labxReport.getPDFFile().getBytes()) {
#Override public String getFilename() { return reportFilename; }
};
MultiValueMap<String, Object> reportFiles = new LinkedMultiValueMap<String, Object>();
reportFiles.add(reportFilename, bas);
HttpHeaders reportFilesReqHeaders = new HttpHeaders();
reportFilesReqHeaders.setContentType(MediaType.APPLICATION_OCTET_STREAM);
HttpEntity<MultiValueMap<String, Object>> reportFilesEntity = new HttpEntity<>(reportFiles, reportFilesReqHeaders);
For the POJO (an instance of ReportInfo here), I create a separate HttpEntity like this:
HttpHeaders reportInfoReqHeaders = new HttpHeaders();
reportInfoReqHeaders.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<ReportInfo> reportInfoEntity = new HttpEntity<>(reportInfo, reportInfoReqHeaders);
Then I cobble together the HttpEntity for my main POST request like this:
MultiValueMap<String, Object> postParams = new LinkedMultiValueMap<String, Object>();
postParams.set("files", reportFilesEntity);
postParams.set("data", reportInfoEntity);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.MULTIPART_FORM_DATA);
httpHeaders.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
HttpEntity<MultiValueMap<String, Object>> requestPOST = new HttpEntity<>(postParams, httpHeaders);
Finally, I make the POST request:
ResponseEntity<String> response = restTemplate.exchange(PORTAL_URL, HttpMethod.POST, requestPOST, String.class);
This results in the following stack trace:
org.springframework.http.converter.HttpMessageNotWritableException: Could not write request: no suitable HttpMessageConverter found for request type [org.springframework.util.LinkedMultiValueMap]
at org.springframework.http.converter.FormHttpMessageConverter.writePart(FormHttpMessageConverter.java:532) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.writeParts(FormHttpMessageConverter.java:503) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.writeMultipart(FormHttpMessageConverter.java:483) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:360) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.http.converter.FormHttpMessageConverter.write(FormHttpMessageConverter.java:156) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate$HttpEntityRequestCallback.doWithRequest(RestTemplate.java:950) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:735) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:672) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:581) ~[spring-web-5.2.16.RELEASE.jar:5.2.16.RELEASE]
...
Thanks.

The way I resolved this issue is shown below in getMessageConverters. The thinking was that the files form parameter was itself a sort of form-based message (ie, with key/value pairs), and therefore required another instance of FormHttpMessageConverter.
Here's the logic that worked for me:
private List<HttpMessageConverter<?>> getMessageConverters()
{
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.TEXT_HTML);
mediaTypes.add(MediaType.APPLICATION_JSON);
mediaTypes.add(MediaType.TEXT_PLAIN);
MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
converter.setSupportedMediaTypes(mediaTypes);
List<MediaType> formMediaTypes = new ArrayList<>();
formMediaTypes.add(MediaType.APPLICATION_FORM_URLENCODED);
formMediaTypes.add(MediaType.MULTIPART_FORM_DATA);
FormHttpMessageConverter formConverter = new FormHttpMessageConverter();
formConverter.setSupportedMediaTypes(formMediaTypes);
formConverter.addPartConverter(new MappingJackson2HttpMessageConverter());
FormHttpMessageConverter multifileConverter = new FormHttpMessageConverter();
multifileConverter.setSupportedMediaTypes(Collections.singletonList(MediaType.APPLICATION_OCTET_STREAM));
multifileConverter.addPartConverter(new ResourceHttpMessageConverter());
formConverter.addPartConverter(multifileConverter);
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setSupportedMediaTypes(formMediaTypes);
List<HttpMessageConverter<?>> messageConverters = new ArrayList<>();
messageConverters.add(converter);
messageConverters.add(formConverter);
messageConverters.add(stringConverter);
return messageConverters;
}

Related

How to change "to-reply" property of email in alfresco

I am able to send emails in alfresco using Java API but I am not able to change the "Reply-to: " property like this in alfresco :
Message replyMessage = new MimeMessage(session);
replyMessage = (MimeMessage) message.reply(false);
replyMessage.setFrom(new InternetAddress(to));
replyMessage.setText("Thanks");
replyMessage.setReplyTo(message.getReplyTo());
replyMessage.setReplyTo(message.getReplyTo());
This is my code to send emails
NodeRef companyHome = repository.getCompanyHome();
List<String> pathElements = new ArrayList<>();
pathElements.add("Data Dictionary");
pathElements.add("Email Templates");
pathElements.add("Trams Email Templates");
pathElements.add("CONTENT_NOTIFICATION.html.ftl");
FileInfo templateFile;
try {
templateFile = serviceRegistry.getFileFolderService()
.resolveNamePath(companyHome, pathElements);
NodeRef template = templateFile.getNodeRef();
List<String> users = new ArrayList<String>();
users.add(userName);
ActionService actionService = serviceRegistry.getActionService();
Action mailAction = actionService.createAction(MailActionExecuter.NAME);
mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, template);
Map<String, Serializable> templateArgs = new HashMap<String, Serializable>();
templateArgs.put("userName", userName);
Map<String, Serializable> templateModel = new HashMap<String, Serializable>();
templateModel.put("args",(Serializable)templateArgs);
mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL,(Serializable)templateModel);
mailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, "Content Notification");
mailAction.setParameterValue(MailActionExecuter.PARAM_TO_MANY, (Serializable) users);
actionService.executeAction(mailAction, null);
} catch (org.alfresco.service.cmr.model.FileNotFoundException e) {
e.printStackTrace();
}
Is there any way to enable this replyTo parameter in alfresco ?
Please help.
I have override the OOTB MailActionExecuter class.
added my code to set the replyTo parameter
I fixed parameter from java class mailAction.setParameterValue(MailActionExecuter.PARAM_REPLY_TO,"myEmail#gmail.com");
and access using
public static final String PARAM_REPLY_TO = "reply_to";
message.setReplyTo(replyTo);

Accessing the returned XML from an API call

I have the following action method to perform an API call:-
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult Create(Rack rack, FormCollection formValues)
{
if (ModelState.IsValid) {
using (var client = new WebClient())
{
var query = HttpUtility.ParseQueryString(string.Empty);
foreach (string key in formValues)
{
query[key] = this.Request.Form[key];
}
query["username"] = "testuser";
query["password"] = ///.....
query["assetType"] = "Rack";
query["operation"] = "AddAsset";
var url = new UriBuilder("http://win-spdev:8400/servlets/AssetServlet");
url.Query = query.ToString();
try
{
string xml = client.DownloadString(url.ToString());
}
The return XML from the API call looks as follow:-
<operation>
<operationstatus>Failure</operationstatus>
<message>Rack already exists.Unable to add</message>
</operation>
but how i can reach the message and operationstaus and according to them to display an appropriate message . i use to serialize the returned Json such as , but i am not sure how to do so for the xML:-
var serializer = new JavaScriptSerializer();
var myObject = serializer.Deserialize<newprocess>(json);
string activityid = myObject.activityId;
Just load it into an XmlDocument.
Untested and from the top of my head:
var xmlDoc = new XmlDocument();
xmlDoc.LoadXml(theXML);
var status = xmlDoc.SelectSingleNode("/operation/operationstatus").InnerText;
var message = xmlDoc.SelectSingleNode("/operation/message").InnerText;
If you using ASP.NET mvc, I believe you can use HttpClient, instead of WebClient:
Define result class:
public class operation
{
public string operationstatus{get;set;}
public string message{get;set;}
}
And then use it for automatic deserilization:
var client = new HttpClient();
var result = client.PostAsync(url,
new FormUrlEncodedContent(new Dictionary<string, string>{
{"username","testuser"},
{"assetType","Rack"}}))
.Result.Content
.ReadAsAsync<operation>().Result;

Faking Http Context with Moq and Mvc

I’m trying to fake http context to test a Controller. My environment is MVC 3 and Moq 4.
So far I have tried a few options including:
a.
var searchController = new MySearchController(_mockResolver.Object.Resolve<IConfiguration>());
var mockContext = new Mock<ControllerContext>();
searchController.ControllerContext = mockContext.Object;
var result = searchController.Render();
b.
var searchController = new MopSearchController(_mockResolver.Object.Resolve<IConfiguration>());
searchController.MockControllerContext();
var result = searchController.Render();
public static class MockHttpHelper
{
public static Mock<HttpContextBase> MockControllerContext(
this Controller controller, string path = null)
{
var mockHttpCtx = MockHttpHelper.MockHttpContext(path);
var requestCtx = new RequestContext(mockHttpCtx.Object, new RouteData());
var controllerCtx = new ControllerContext(requestCtx, controller);
controller.ControllerContext = controllerCtx;
return mockHttpCtx;
}
public static Mock<HttpContextBase> MockHttpContext(string path)
{
var mockHttpCtx = new Mock<HttpContextBase>();
var mockReq = new Mock<HttpRequestBase>();
mockReq.SetupGet(x => x.RequestType).Returns("GET");
mockReq.SetupGet(req => req.Form).Returns(new NameValueCollection());
mockReq.SetupGet(req => req.QueryString).Returns(new NameValueCollection());
mockHttpCtx.SetupGet(x => x.Request).Returns(mockReq.Object);
return mockHttpCtx;
}
}
Neither of these work, I get the exception below. Can anyone point me in the direction of a working example? I’ve seen quite a few questions on the net around the same topic, but given the date (posts from 2008-2010) and MVC version (i.e. 1 and 2) I feel like I’m missing something / or trying to mock more than I need to in MVC3.
System.NullReferenceException : Object reference not set to an instance of an object.
at System.Web.Mvc.ChildActionValueProviderFactory.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.ValueProviderFactoryCollection.<>c__DisplayClassc.<GetValueProvider>b__7(ValueProviderFactory factory)
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()
at System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)
at System.Linq.Enumerable.ToList(IEnumerable`1 source)
at System.Web.Mvc.ValueProviderFactoryCollection.GetValueProvider(ControllerContext controllerContext)
at System.Web.Mvc.Controller.TryUpdateModel(TModel model)
Thanks
Yes, all you were really missing, as you've noted, was setting the Controller's ValueProvider. Even though you're using this controller with a Get action but no Post action, the Controller still gets its ValueProvider instantiated upon creation, so you need to do the same thing in your test scenario. Here's the base class that I use when testing my controllers. I use NBehave's NUnit wrapper for unit testing, so ignore the SpecBase reference if you wish
public abstract class MvcSpecBase<T> : SpecBase<T> where T : Controller
{
protected T Controller { get; set; }
protected string RelativePath = string.Empty;
protected string AbsolutePath = string.Empty;
protected void InitialiseController(T controller, NameValueCollection collection, params string[] routePaths)
{
Controller = controller;
var routes = new RouteCollection();
RouteConfig.RegisterRoutes(routes);
var httpContext = ContextHelper.FakeHttpContext(RelativePath, AbsolutePath, routePaths);
var context = new ControllerContext(new RequestContext(httpContext, new RouteData()), Controller);
var urlHelper = new UrlHelper(new RequestContext(httpContext, new RouteData()), routes);
Controller.ControllerContext = context;
Controller.ValueProvider = new NameValueCollectionValueProvider(collection, CultureInfo.CurrentCulture);
Controller.Url = urlHelper;
}
}
Then, in your test, create your controller and then call this line:
InitialiseController(controller, new FormCollection());

How to Pass custom objects using Spring's REST Template

I have a requirement to pass a custom object using RESTTemplate to my REST service.
RestTemplate restTemplate = new RestTemplate();
MultiValueMap<String, Object> requestMap = new LinkedMultiValueMap<String, Object>();
...
requestMap.add("file1", new FileSystemResource(..);
requestMap.add("Content-Type","text/html");
requestMap.add("accept", "text/html");
requestMap.add("myobject",new CustomObject()); // This is not working
System.out.println("Before Posting Request........");
restTemplate.postForLocation(url, requestMap);//Posting the data.
System.out.println("Request has been executed........");
I'm not able to add my custom object to MultiValueMap. Request generation is getting failed.
Can someone helps me to find a way for this? I can simply pass a string object without problem.User defined objects makes the problem.
Appreciate any help !!!
You can do it fairly simply with Jackson.
Here is what I wrote for a Post of a simple POJO.
#XmlRootElement(name="newobject")
#JsonSerialize(include=JsonSerialize.Inclusion.NON_NULL)
public class NewObject{
private String stuff;
public String getStuff(){
return this.stuff;
}
public void setStuff(String stuff){
this.stuff = stuff;
}
}
....
//make the object
NewObject obj = new NewObject();
obj.setStuff("stuff");
//set your headers
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
//set your entity to send
HttpEntity entity = new HttpEntity(obj,headers);
// send it!
ResponseEntity<String> out = restTemplate.exchange("url", HttpMethod.POST, entity
, String.class);
The link above should tell you how to set it up if needed. Its a pretty good tutorial.
To receive NewObject in RestController
#PostMapping("/create") public ResponseEntity<String> createNewObject(#RequestBody NewObject newObject) { // do your stuff}
you can try this
public int insertParametro(Parametros parametro) throws LlamadasWSBOException {
String metodo = "insertParam";
String URL_WS = URL_WS_BASE + metodo;
Integer request = null;
try {
logger.info("URL_WS: " + URL_WS);
request = restTemplate.postForObject(URL_WS, parametro, Integer.class);
} catch (RestClientResponseException rre) {
logger.error("RestClientResponseException insertParametro [WS BO]: " + rre.getResponseBodyAsString());
logger.error("RestClientResponseException insertParametro [WS BO]: ", rre);
throw new CallWSBOException(rre.getResponseBodyAsString());
} catch (Exception e) {
logger.error("Exception insertParametro[WS BO]: ", e);
throw new CallWSBOException(e.getMessage());
}
return request;
}

How to convert HashMap<String,Integer> param to Map<String,Object)

I want to call JasperFillManager.fillReport(filePath+".jasper", param, con); where param is supposed to accept type Map. is there any solution
Simply use the constructor taking another map as an argument:
Map<String, Object> map2 = new HashMap<String, Object>(map);
See this example:
import java.util.HashMap;
import java.util.Map;
public class Test5 {
public static void main(String[] args) {
HashMap<String, Integer> map = new HashMap<String, Integer>();
map.put("1", 1);
Map<String, Object> map2 = new HashMap<String, Object>(map);
// etc...
}
}
If you have a HashMap<String,Integer> and you need to convert it to a HashMap<String,Object>, then the following should work:
HashMap<String, Object> objParams = new HashMap<String, Object>();
for (String key : intParams.keyValues()) {
Integer intValue = intParams.get(key);
objParams.put(key, intValue);
}
Where the intParams is your HashMap<String,Integer>.
There might be some typos in there as this is purely off the cuff.
Then you can pass the objParams to fillReport.
If you don't specify the value type in HashMap, Java implicitly gives it a type of object.
In that case you just need to declare the original map as
HashMap objParams = new HashMap();
Your .java file
String url="jdbc:mysql://127.0.0.1:3306/database";
String username="root";
String password="";
String database="database";
Class.forName("com.mysql.jdbc.Driver").newInstance();
Connection con = DriverManager.getConnection(url, username, password);
JasperReport jc=JasperCompileManager.compileReport("F:\\pro\\report.jrxml"); //give your report.jrxml file path
//create hashmap to send data to report.key should same to report parameter
HashMap para = new HashMap();
para .put("name", "chamod");
para .put("email", "chamodck#gmail.com");
JasperPrint print = JasperFillManager.fillReport(jc,para,new JREmptyDataSource());
JasperViewer.viewReport(print);
con.close();
Your report.jrxml file text fields should be like this
<textFieldExpression class="java.lang.String"><![CDATA[$P{name}]]></textFieldExpression>
<textFieldExpression class="java.lang.String"><![CDATA[$P{email}]]></textFieldExpression>

Resources