EXTJS and Spring MVC Portlet File Upload - spring

I have successfully been able to upload a file to my ActionResponse method in my controller for my spring portlet using extjs. My problem is that extjs is expecting a json response in order to know the upload is complete, and the response for my action request renders out the entire portal.
I am needing a way to tell the response for my request to only be a json string.
here is my controller.
#Controller
#RequestMapping(value = "VIEW")
public class MediaRoomViewController {
private static final Logger _log = Logger.getLogger(MediaRoomViewController.class);
#RenderMapping
public String renderView(RenderRequest request, RenderResponse response, Model model){
return "view";
}
#InitBinder
protected void initBinder(PortletRequest request, PortletRequestDataBinder binder) throws Exception {
// to actually be able to convert Multipart instance to byte[]
// we have to register a custom editor
binder.registerCustomEditor(byte[].class, new ByteArrayMultipartFileEditor());
// now Spring knows how to handle multipart object and convert
}
#ActionMapping(params="action=uploadFile")
public String uploadFile(FileUploadBean uploadItem, BindingResult result,ActionResponse response) throws Exception {
_log.debug("Upload File Action");
ExtJSFormResult extjsFormResult = new ExtJSFormResult();
try{
if (result.hasErrors()){
for(ObjectError error : result.getAllErrors()){
System.err.println("Error: " + error.getCode() + " - " + error.getDefaultMessage());
}
//set extjs return - error
extjsFormResult.setSuccess(false);
}
// Some type of file processing...
System.err.println("-------------------------------------------");
System.err.println("Test upload: " + uploadItem.getFile().getOriginalFilename());
System.err.println("-------------------------------------------");
//set extjs return - sucsess
extjsFormResult.setSuccess(true);
}catch(Exception ex){
_log.error(ex,ex);
}
return extjsFormResult.toString();
}
here is my extjs file upload code
Ext.create('Ext.form.Panel', {
title: 'File Uploader',
width: 400,
bodyPadding: 10,
frame: true,
renderTo: self._Namespace + 'file-upload',
items: [{
xtype: 'filefield',
name: 'file',
fieldLabel: 'File',
labelWidth: 50,
msgTarget: 'side',
allowBlank: false,
anchor: '100%',
buttonText: 'Select a File...'
}],
buttons: [{
text: 'Upload',
handler: function() {
var form = this.up('form').getForm();
if(form.isValid()){
form.submit({
url: self._Urls[0],
waitMsg: 'Uploading your file...',
success: function(form,action) {
alert("success");
},
failure: function(form,action){
alert("error");
}
});
}
}
}]
});

There are several ways to go here:
Manually force response as text like this
response.setContentType("text/html"); //or json if u wish
responseText = "{'success':'true'}";
response.getWriter().write(responseText);
Use Jackson lib as covered here:
Spring 3.0 making JSON response using jackson message converter
Move on to Grails and forget about verbose MVC configs. My preferred method :)

Related

Highchart in Spring boot

I try to use hightchart in spring boot to plot a line graph. But I can't get line chart but only show blank result in my view.
This is my view side
$.ajax({
url:'${pageContext.request.contextPath}/hawker/linechartdata',
dataType: 'json'
success: function(result){
var month = JSON.parse(result).month;
var report = JSON.parse(result).report;
drawLineChart(month, report);
}
})
function drawLineChart(month,report){
document.addEventListener('DOMContentLoaded', function () {
var chart = Highcharts.chart('container', {
chart: {
type: 'line'
},
title: {
text: 'Fruit Consumption'
},
xAxis: {
categories: month
},
yAxis: {
title: {
text: 'Fruit eaten'
}
},
series: [{
name: 'Jane',
data: report
}]
});
});
}
<!--Here to show the graph -->
<div id="container"></div>
My controller that use Json
#RequestMapping("/linechartdata")
#ResponseBody
public String getDataFromDb(#AuthenticationPrincipal UserDetails user) {
User currentuser = userRepository.findByUsername(user.getUsername());
Optional<Hawker> hawker = hawkerService.getHawkerByUserId(currentuser.getId());
//Get the report list
List<Report> reportList = reportService.findByHawId(hawker.get().getHaw_id());
JsonArray jsonMonth = new JsonArray();
JsonArray jsonReport = new JsonArray();
JsonObject json = new JsonObject();
reportList.forEach(report->{
jsonMonth.add(report.getMonth()+1);
jsonReport.add(report.getTotal_sales());
});
json.add("month", jsonMonth);
json.add("report", jsonReport);
return json.toString();
When I remove the ajax and add in the manual data, it do show the graph. Please help me, Thank you.
You don't need to manually create json in your controller code, spring boot will handle it for you. You should create a dto class in a form which is expected by your javascript.
Which is in your case:
public class LineChartDto {
private List<Integer> month; // you better call this "months" or "monthList"
private List<BigDeciamal> report; // why do you call this "report" while this is actually "sales"
// all args constructor, getters
}
And your controller method would be:
#RequestMapping("/linechartdata")
#ResponseBody // or use #RestController
public LineChartDto getSalesLineChartData(..) {
List<Report> reportList = ..
List<Integer> months = reportList.stream()
.map(report -> report.getMonth()+1)
.collect(Collectors.toList());
List<BigDecimal> sales = reportList.stream()
.map(Report::getTotal_sales) // better name this getTotalSales
.collect(Collectors.toList());
return new LineChartDto(months, sales);
}
Response will result in json object:
{
"month": [1, 2, ... ],
"report": [100, 200, ... ]
}
As for why you ajax doesn't work - the question is too broad. Start with Chrome Dev Tools Network to check what network communication is happening, check browser console for errors, add some console.log() or better debug your js.
This line document.addEventListener('DOMContentLoaded', function () looks suspicious to me. I don't think you need to addEventListener each time you call your chart.
Probably you should do this:
function drawLineChart(month,report){
Highcharts.chart('container', {
...

Converting working Ajax call to Angular Observable

I have an existing JavaScript application that submits documents (.pdf, .txt...) to Solr for text extraction. I am now trying to convert that capability to an Angular-6 implementation and am struggling with the whole observable pattern. Below is the working js code, followed by my angular component and service .ts files. I think I'm close, but no cigar.
let myReader = new FileReader();
myReader.onloadend = function() {
fileAsBlob = myReader.result;
sendToSolr(fileAsBlob);
};
fileAsBlob = myReader.readAsArrayBuffer(file);
/* Get the unique Id for the doc and append to the extract url*/
let docId = $("#post_docId").val();
let extractUrl = "http://localhost:8983/solr/" + core + "/update/extract/?commit=true&literal.id=" + docId;
/* Ajax call to Solr/Tika to extract text from pdf and index it */
function sendToSolr(fileAsBlob) {
$.ajax({
url: extractUrl,
type: 'POST',
data: fileAsBlob,
cache: false,
jsonp: 'json.wrf',
processData: false,
contentType: false,
echoParams: "all",
success: function(data, status) {
//console.log("Ajax.post successful, status: " + data.responseHeader.status + "\t status text: " + status);
//console.log("debug");
getDocument(docId);
},
error: function(data, status) {
//console.log("Ajax.post error, status: " + data.status + "\t status text:" + data.statusText);
},
done: function(data, status) {
//console.log("Ajax.post Done");
},
});
}
All the above does is use a fileReader to read a local file into an ArrayBuffer, and submits that ArrayBuffer to Solr via an Ajax call. In my success I do call another function (getDocument) which just queries Solr (By docId) for the document I just submitted and displays it. Not beautiful, but it works.
For the angular version I have the following service:
constructor(private http: HttpClient) { }
postDocToSolr(fileAsBlob: any): Observable<any> {
let httpHeaders = new HttpHeaders()
.set('type' , 'POST')
.set('jsonp', 'json.wrf')
.set('processData', 'false')
.set('echoParams', 'all')
.set('Content-Type', 'application/x-www-form-urlencoded')
.set('charset', 'utf-8')
.set('data', fileAsBlob);
let options = {
headers: httpHeaders
};
return this.http.post(this.extractUrl, fileAsBlob, options);
}
}
I tried posting the entire service, but it threw the formatting off so here is the POST part of the service.
And in my component I call the service:
extractText(fileContents: any) {
console.log("In the Document.extractText() method");
//this.processDocument(fileContents);
this.textExtractor.postDocToSolr(fileContents)
.subscribe(data => {
console.log("Debug");
console.log("Data: ") + data;
},
error => {
console.log("Error" + error);
}
);
console.log("Debug");
}
Note I've done the fileReader already and am submitting basically the same ArrayBuffer.
The only hint is the in the error => log the error callback(Right term?)on the observable. I get an error code 400, bad request with the message:
"msg":"URLDecoder: Invalid digit (P) in escape (%) pattern"
Which doen't help me much. I'm wondering if it's an encoding issue (UTF-8) but not sure where to begin. Would appreciate a nudge in the right direction.
It looks like the problem is how angular is encoding your URI, I would open your network tool of choice (network tab, fiddler, etc) and look at the sent request URI of each. I suspect they'll be different.
Well, it's often the small things that trip me up. I needed to set the Content-Type to "false" and everything works fine. I also re-did the creation of the httpHeaders, but I think either way would have worked. The working postToSolr Service method is:
export class TextExtractorServiceService {
extractUrl: string = "http://localhost:8983/solr/tater/update/extract/?commit=true&literal.id=778";
constructor(private http: HttpClient) { }
postDocToSolr(fileAsBlob: any): Observable<any> {
const httpOptions = {
headers: new HttpHeaders({
"jsonp": "json.wrf",
"processData": "false",
"echoParams" : "all",
"Content-Type": "false",
"cache": "false"
})
};
return this.http.post(this.extractUrl, fileAsBlob, httpOptions);
}
}
Thanks to all who took a look.

ExtDirectSpring Router URL?

I'm trying to get ExtDirectSpring to work but can't figure out what the URL to the Direct Router shall be.
My controller:
#Controller
#RequestMapping(value = "/sonstiges/extdirect")
#Transactional
public class ExtDirectController {
#RequestMapping(value = "")
public String start() throws Exception {
return "/sonstiges/extdirect";
}
#ExtDirectMethod(value = ExtDirectMethodType.STORE_READ)
public List<String> loadAllMyStuff() {
return Arrays.asList(new String[] {"one", "two"});
}
}
In my JSP I add a provider like this:
Ext.direct.Manager.addProvider({
"type":"remoting", // create a Ext.direct.RemotingProvider
"url":"/fkr/app/controller/extjs/remoting/router", // url to connect to the Ext.Direct server-side router.
"actions":{ // each property within the actions object represents a Class
"ExtDirectController":[
// array of methods within each server side Class
{
"name":"loadAllMyStuff", // name of method
"len":0
},
{
"name":"myTestFunction", // name of method
"len":0
}
]
},
"namespace":"FKR"// namespace to create the Remoting Provider in
});
... and use the following store to populate a grid:
store: {
model: 'Company',
remoteSort: true,
autoLoad: true,
sorters: [
{
property: 'turnover',
direction: 'DESC'
}],
proxy: {
type: 'direct',
directFn: FKR.ExtDirectController.loadAllMyStuff
}
}
Loading the page, a request to http://localhost:8080/fkr/app/controller/extjs/remoting/router is send, but not to my loadAllMyStuff- function. My guess is that the URL to the direct router is wrong.
What is the correct URL to the router?
EDIT: I just figured out that the method router in RouterController expects the parameters extAction and extMethod, but with the given code the parameters action and method are sent. That seems to be odd ...

Spring Security & ExtJS - redirecting to login page on session timeout

I am using ExtJS with Spring MVC/Security. I want the user to be redirected to the login page when the session has expired, and I gave this in the Spring security application context -
<session-management invalid-session-url="/login.jsp"></session-management>
But since the calls to the server are all AJAX based, the redirection does not happen.
Please suggest the best way to implement this.
I have a custom UserNamePasswordAuthenticationFilter implemented for AJAX login:
#Override
protected void successfulAuthentication(HttpServletRequest request,
HttpServletResponse response, Authentication authResult) throws IOException,
ServletException {
SavedRequestAwareAuthenticationSuccessHandler srh = new SavedRequestAwareAuthenticationSuccessHandler();
this.setAuthenticationSuccessHandler(srh);
srh.setRedirectStrategy(new RedirectStrategy() {
#Override
public void sendRedirect(HttpServletRequest httpServletRequest,
HttpServletResponse httpServletResponse, String s) throws IOException {
// do nothing, no redirect
}
});
super.successfulAuthentication(request, response, authResult);
HttpServletResponseWrapper responseWrapper = new HttpServletResponseWrapper(
response);
Writer out = responseWrapper.getWriter();
out.write("{success:true}");
out.close();
}
You might be able to mold the following to override all ajax requests to test for a timed out session response and handle it accordingly:
var origHandleResponse = Ext.data.Connection.prototype.handleResponse;
Ext.override(Ext.data.Connection, {
handleResponse : function(response){
var text = Ext.decode(response.responseText);
if (<test for response that means the session timed out>)
{
var login = new Ext.Window({
plain: true,
closeAction: 'hide',
modal: true,
title: "Login timed out, please log in.",
width: 400,
autoHeight: true,
items: [
{
xtype: 'form',
id: 'login-form',
items: [
{
xtype: 'textfield',
fieldLabel: 'Username',
name: 'username'
},
{
xtype: 'textfield',
inputType: 'password',
fieldLabel: 'Password',
name: 'password'
}]
}],
buttons: [
{
text: 'Submit',
handler: function() {
Ext.getCmp('login-form').getForm().submit({url: '<login url>'});
login.hide();
}
}]
});
login.show();
}
//else (optional?)
origHandleResponse.apply(this, arguments);
}
});

Jqgrid spring date binding exception

OS: Windows Vista, Framework: Jqgrid (latest), Spring (latest), JQuery (latest)
I am using Jqgrid to post a form to Spring Controller to persist. When the Spring controller tries to auto bind the request parameters to domain object, it throws exception when trying to bind 'Date' data type. I am using JSon format for transfer data. The Jqgrid display the date correctly. The transfer string contains '&-quot;' characters before and after the date that causes the exception. I dont know how to remove the escape character from Jqgrid. I dont know how to intercept the string before Spring gets a chance to auto bind. Thanks for your help in advance.
public class JsonDateSerializer extends JsonSerializer<Date> {
private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
AtOverride
public void serialize(Date date, JsonGenerator gen, SerializerProvider provider)
throws IOException, JsonProcessingException {
String formattedDate = dateFormat.format(date);
gen.writeString(formattedDate);
}
}
My controller class has initBinder method.
#InitBinder
public void initBinder(WebDataBinder binder) {
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
dateFormat.setLenient(false);
binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, false));
binder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
}
Exception stack trace
nested exception is org.springframework.core.convert.ConversionFailedException: Unable to convert value "2010-12-01 11:10:00" from type 'java.lang.String' to type 'java.util.Date'; nested exception is java.lang.IllegalArgumentException]
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.doBind(HandlerMethodInvoker.java:820)
org.springframework.web.bind.annotation.support.HandlerMethodInvoker.resolveHandlerArguments(HandlerMethodInvoker.java:359)
I will try to set-up a tutorial on my blog if I get the time today.
My JSP file is just a simple JSP file with your typical JqGrid declaration:
<script type="text/javascript">
jq(function() {
// This is the grid
jq("#grid").jqGrid({
url:'/myapp/users',
datatype: 'json',
mtype: 'GET',
colNames:['Id','Username','First Name'],
colModel:[
{name:'id',index:'id', width:55,editable:false,editoptions:{readonly:true,size:10},hidden:true},
{name:'firstName',index:'firstName', width:100,editable:true, editrules:{required:true}, editoptions:{size:10}, editrules:{required:true}},
{name:'lastName',index:'lastName', width:80, align:"right",editable:true, editrules:{required:true}, editoptions:{size:10}, editrules:{required:true}}
],
postData: {
// Here you can post extra parameters
// For example using JQuery you can retrieve values of other css elements
},
rowNum:10,
rowList:[10,20,30],
height: 200,
autowidth: true,
rownumbers: true,
pager: '#pager',
sortname: 'id',
viewrecords: true,
sortorder: "asc",
caption:"Users",
emptyrecords: "Empty records",
loadonce: false,
loadComplete: function() {
// Here you can provide extra functions after the grid is loaded completely
// Like auto-height function
},
jsonReader : {
root: "rows",
page: "page",
total: "total",
records: "records",
repeatitems: false,
cell: "cell",
id: "id"
}
});
// This is the pager
jq("#grid").jqGrid('navGrid','#pager',
{edit:false,add:false,del:false,search:true},
{ },
{ },
{ },
{
sopt:['eq', 'ne', 'lt', 'gt', 'cn', 'bw', 'ew'],
closeOnEscape: true,
multipleSearch: true,
closeAfterSearch: true }
);
// Custom Add button on the pager
jq("#grid").navButtonAdd('#pager',
{ caption:"Add",
buttonicon:"ui-icon-plus",
onClickButton: addRow,
position: "last",
title:"",
cursor: "pointer"
}
);
// Custom Edit button on the pager
jq("#grid").navButtonAdd('#pager',
{ caption:"Edit",
buttonicon:"ui-icon-pencil",
onClickButton: editRow,
position: "last",
title:"",
cursor: "pointer"
}
);
// Custom Delete button on the pager
jq("#grid").navButtonAdd('#pager',
{ caption:"Delete",
buttonicon:"ui-icon-trash",
onClickButton: deleteRow,
position: "last",
title:"",
cursor: "pointer"
}
);
// Toolbar Search
jq("#grid").jqGrid('filterToolbar',{stringResult: true,searchOnEnter : true, defaultSearch:"cn"});
});
Take note I'm using JQuery's noConflict method here. Since I have another Javascript framework that uses the $, I forced JQuery to use a different identifier for itself. I picked jq and here's the declaration:
<script type="text/javascript">
var jq = jQuery.noConflict();
</script>
The Javascript's above can be declared on the head section of your JSP. The critical part of the JqGrid declaration is the jsonReader and the datatype. Make sure the colModel name matches your model properties.
You could probably use StringTrimmerEditor's second constructor to delete those extra chars
StringTrimmerEditor(String charsToDelete, boolean emptyAsNull)
Based on the Spring docs:
charsToDelete - a set of characters to delete, in addition to trimming an input String. Useful for deleting unwanted line breaks. E.g. "\r\n\f" will delete all new lines and line feeds in a String.
I also use the latest version of JqGrid and Spring 3, but the way I handled the parameters seems to be simpler. Here's how I did it:
#Controller
#RequestMapping("/json")
public class JsonController {
#RequestMapping(method = RequestMethod.GET)
public #ResponseBody JsonResponse getAll(
#RequestParam("_search") String search,
#RequestParam(value="filters", required=false) String filters,
#RequestParam(value="datefrom", required=false) String datefrom,
#RequestParam(value="dateto", required=false) String dateto,
#RequestParam(value="page", required=false) String page,
#RequestParam(value="rows", required=false) String rows,
#RequestParam(value="sidx", required=false) String sidx,
#RequestParam(value="sord", required=false) String sord
) { ... }
All parameters are passed as normal Strings. The datefrom and dateto are custom parameters I passed from the JqGrid. The date comes from a JQuery's DatePicker and passed via JqGrid postData:
postData: {
datefrom: function() { return jq("#datepicker_from").datepicker("getDate"); },
dateto: function() { return jq("#datepicker_to").datepicker("getDate"); }
},
JsonResponse is a simple POJO that I used to map the search parameters passed by a JqGrid:
public class JsonResponse {
/**
* Current page of the query
*/
private String page;
/**
* Total pages for the query
*/
private String total;
/**
* Total number of records for the query
*/
private String records;
/**
* An array that contains the actual data
*/
private List<MyDTO> rows;
public JsonResponse() {
}
public String getPage() {
return page;
}
public void setPage(String page) {
this.page = page;
}
public String getTotal() {
return total;
}
public void setTotal(String total) {
this.total = total;
}
public String getRecords() {
return records;
}
public void setRecords(String records) {
this.records = records;
}
public List<MyDTO> getRows() {
return rows;
}
public void setRows(List<MyDTO> rows) {
this.rows = rows;
}
}
MyDTO is a simple DTO object as well.
In my Spring applicationContext.xml, I just have to declare the following:
<mvc:annotation-driven/>
And added the Jackson jar for converting from JSON to POJO and vice versa
To convert the String date to a real Date, I used the great Joda library. But of course, you can use JDK's standard Date.
If you notice on my JSP, I added custom buttons. The onClickButton calls another Javascript function. For example on the Add button, I have the addRow function. Here's the function declaration:
function addRow() {
// Get the currently selected row
jq("#grid").jqGrid('editGridRow','new',
{ url: "/myapp/users/add",
editData: {
// Here you add extra post parameters
},
recreateForm: true,
beforeShowForm: function(form) {
// Here you can add, disable, hide elements from the popup form
},
closeAfterAdd: true,
reloadAfterSubmit:false,
afterSubmit : function(response, postdata)
{
// This is a callback function that will evaluate the response sent by your Controller
var result = eval('(' + response.responseText + ')');
var errors = "";
if (result.success == false) {
// Do whatever you like if not successful
} else {
// Do whatever you like if successful
}
// only used for adding new records
var new_id = null;
// Then this will be returned back to the popup form
return [result.success, errors, new_id];
}
});
}
Take note of the url:
url: "/myapp/users/add"
This is the mapping to your Controller that handles the add request
Now, for the Jackson serialization/deserialization, you'll be suprised how easy it is.
First, make sure you have the latest Jackson library in your classpath. Next, open your Spring xml config, and add the following:
<mvc:annotation-driven/>
That's it :)
Make sure you have the latest dependency jars. I use Spring 3.0.4. There's 3.0.5 already. Also, make sure you have the latest aspectjweaver.jar. If you wanna know what's inside that mvc-annotation-driven tag, just search the web and you'll see what it contains. Basically it automatically declares an HTTPMessageConverter for JSON, and uses Jackson by default.
For the Controller, here's the mapping to the /myapp/users/add request:
#Controller
#RequestMapping("/myapp/users")
public class UserController {
#RequestMapping(value = "/add", method = RequestMethod.POST)
public #ResponseBody JsonGenericResponse addAsJson(
#RequestParam("id") String id,
#RequestParam("firstName") String firstName,
#RequestParam("lastName") String lastName
) {
// Validate input
// Process data. Call a service, etc.
// Send back a response
// JsonGenericResponse is a custom POJO
// Jackson will automatically serialize/deserialize this POJO to a JSON
// The #ResponseBody annotation triggers this behavior
JsonGenericResponse response = new JsonGenericResponse();
response.setSuccess(false);
response.setMessage("Error in server");
return response;
}
}
Here's the JsonGenericResponse:
public class JsonGenericResponse {
private Boolean success;
private List<String> message;
public JsonGenericResponse() {
message = new ArrayList<String>();
}
public Boolean getSuccess() {
return success;
}
public void setSuccess(Boolean success) {
this.success = success;
}
public List<String> getMessage() {
return message;
}
public void setMessage(String message) {
this.message.add(message);
}
}
It's just really a simple POJO.
In your JSP, to process the response, you use JavaScript in your JqGrid. The response will be sent back to whomever the caller (the add form in this case). Here's a sample JavaScript that will process the response:
if (result.success == false) {
for (var i = 0; i < result.message.length; i++) {
errors += result.message[i] + "<br/>";
}
} else {
jq("#dialog").text('Entry has been edited successfully');
jq("#dialog").dialog(
{ title: 'Success',
modal: true,
buttons: {"Ok": function() {
jq(this).dialog("close");}
}
});
}
return [result.success, errors, null];

Resources