Can I use this method on different ports? Its working on port :8080 and i can download file but on :4200 i see only logs and nothing more.
Method:
#GetMapping("/downloadJson")
public ResponseEntity<byte[]> downloadJsonFile() {
List<Wine> wines = wineService.findAllWines();
String wineJsonString = jsonExporter.export(wines);
byte[] wineJsonBytes = wineJsonString.getBytes();
return ResponseEntity
.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=wines.json")
.contentType(MediaType.APPLICATION_JSON)
.body(wineJsonBytes);
}
HTML:
<a class="nav-link" (click)="downloadFile()">Download<span class="sr-only">(current)</span></a>
Service:
public downloadJsonFile(): Observable<any> {
return this.http.get<any>(`${this.apiServerUrl}/wine/downloadJson`)
}
Component:
public downloadFile() {
this.wineService.downloadJsonFile().subscribe();
}
I had to change download method in component.ts:
public downloadFile(): void {
this.wineService
.download()
.subscribe(blob => saveAs(blob, 'wine.json'));
}
Service:
public download(): Observable<Blob> {
return this.http.get(`${environment.apiBaseUrl}/wine/downloadJson`, {
responseType: 'blob'
});
}
To use 'saveAs' in component i had to download new package:
npm install --save file-saver
and import in app.component.ts
import { saveAs } from 'file-saver';
Related
I am working on a Java application, and the functionality I am working on is to download a file after populating it's content from the database when the user clicks on export button, the file is generated correctly, the problem I have is when I try to set ByteArrayResource into the ResponseEntity, when I call the webservice from angular, I get Byte array resource [resource loaded from byte array] cannot be resolved to absolute file path, here is my code :
Controller method :
#GetMapping(value = "/exportods/{id}")
public ResponseEntity<ExportDTO> exportODSFile(#PathVariable String id)
throws InvalidationRequestNotFoundException, IOException, ExportInvalidationRequestException {
File odsFfile = null;
ByteArrayResource resource = null;
ExportInvalidationRequestDTO exportInvalidationRequestDTO = new ExportInvalidationRequestDTO();
try {
//getting the file
odsFfile = exportService.exportInvalidationRequest(id);
Path pathObj = Paths.get(odsFfile.getCanonicalPath());
//creating the resource
resource = new ByteArrayResource(Files.readAllBytes(pathObj));
exportDTO.setResource(resource);
} catch (IOException e) {
exportDTO.setError(new ErrorDTO(HttpStatus.NOT_FOUND.value()));
}
return ResponseEntity.ok(exportDTO);
}
ExportDTO :
public class ExportDTO extends AbstractReturnDTO {
private ByteArrayResource resource;
public ExportDTO (ByteArrayResource resource) {
this.resource = resource;
}
public ExportDTO () {
}
public ByteArrayResource getResource() {
return resource;
}
public void setResource(ByteArrayResource resource) {
this.resource = resource;
}
}
Angular component method:
async downloadODSFile(id) {
await this.requestService.downloadODSFile(id).subscribe((requestExportODS: RequestExportODS) => {
if (!requestExportODS) {
if(requestExportODS.error == null){
//handle error here
}
else{
saveAs(new Blob([requestExportODS.file], { type: \'application/vnd.oasis.opendocument.spreadsheet\'' }), id );
}
}
});
}
Once I click on the export button, I get an error :
error: "Internal Server Error"
message: "Byte array resource [resource loaded from byte array] cannot be resolved to absolute file path"
Set the observe as response in the http service method
//requestService
downloadODSFile(id) {
return this.http.get<HttpResponse<any>>(`/exportods/${id}`, { observe: 'response' })
}
And use it like this
async downloadODSFile(id) {
await this.requestService.downloadODSFile(id).subscribe(res => {
console.log(res.body) //Byte array
});
}
BTW async and await are redundant in your code, as you haven't converted observable to promise.
I'm trying to use nvd3d candlestick chart with Angular, but I'm not getting to render it when using a rest service built in Java.
How to consume a java rest to render nv3d candlestick chart with Angular?
My rest is returning this:
[{"id":450,"vwap":3821.62,"faixa":69.48,"open":3858.7,"high":3863.29,"low":3793.81,"close":3795.54,"date":19338}]
The component expected this:
[{values:[{"id":450,"vwap":3821.62,"faixa":69.48,"open":3858.7,"high":3863.29,"low":3793.81,"close":3795.54,"date":19338}]}]
My Angular code:
import { Injectable } from '#angular/core';
import { Provider, SkipSelf, Optional, InjectionToken } from '#angular/core';
import { Response, Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';
import { HttpInterceptorService, RESTService } from '#covalent/http';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/catch';
import 'rxjs/add/operator/do';
export interface IDolFutDiario {
id: number;
date: number;
open: number;
high: number;
low: number;
close: number;
vwap: number;
faixa: number;
}
#Injectable()
export class DolfudiarioService extends RESTService<IDolFutDiario>{
constructor(private _http: HttpInterceptorService) {
super(_http, {
baseUrl: 'http://localhost:8080',
path: '',
});
}
staticQuery(): Observable<IDolFutDiario[]> {
return this.http.get('http://localhost:8080/dolfutdiarios')
.map(this.extractData)
.catch(this.handleErrorObservable);
}
extractData(res: Response) {
let body = res.json();
return body;
}
private handleErrorObservable (error: Response | any) {
console.error(error.message || error);
return Observable.throw(error.message || error);
}
}
My Java code:
#RestController
public class DolFutRestController {
#Autowired
DolFutDiarioService dolFutDiarioService;
#RequestMapping(value = "dolfutdiarios", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<List<DolFutDiario>> list() {
List<DolFutDiario> dolfutdiarios = dolFutDiarioService.listDolFutDiarios();
return ResponseEntity.ok().body(dolfutdiarios);
}
}
PS: When I put the second block of data [[values: ..... , it works.
However when I get from Java Service it does not.
No errors returned as well.
Well, you need to convert the block of data you get to the one you want. It's not going to work if you use the wrong format. The crux of the matter is in this method:
extractData(res: Response) {
let body = res.json();
return body;
}
There you can map your data to what you need; for example, if you want to wrap it in a values object, do it like so:
extractData(res: Response) {
const body = res.json();
return [{ values: body }];
}
Also, try console.log'ing your code in different steps to see what you have and compare to what you need!
How do I create an Angular 4 client for a Java Project Reactor reactive Flux API? The sample below has two APIs: a Mono API; and, Flux API. Both work from curl; but in Angular 4 (4.1.2) only the Mono API works; any ideas how to get Angular 4 to work with the Flux API?
Here's a trivial Spring Boot 2.0.0-SNAPSHOT application with a Mono API and a Flux API:
#SpringBootApplication
#RestController
public class ReactiveServiceApplication {
#CrossOrigin
#GetMapping("/events/{id}")
public Mono<Event> eventById(#PathVariable long id) {
return Mono.just(new Event(id, LocalDate.now()));
}
#CrossOrigin
#GetMapping(value = "/events", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<Event> events() {
Flux<Event> eventFlux = Flux.fromStream(
Stream.generate(
()->new Event(System.currentTimeMillis(), LocalDate.now()))
);
Flux<Long> durationFlux = Flux.interval(Duration.ofSeconds(1));
return Flux.zip(eventFlux, durationFlux).map(Tuple2::getT1);
}
public static void main(String[] args) {
SpringApplication.run(ReactiveServiceApplication.class);
}
}
with a Lombok-ed event:
#Data
#AllArgsConstructor
public class Event {
private final long id;
private final LocalDate when;
}
These reactive APIs work from curl as I'd expect:
jan#linux-6o1s:~/src> curl -s http://localhost:8080/events/123
{"id":123,"when":{"year":2017,"month":"MAY","monthValue":5,"dayOfMonth":15,"dayOfWeek":"MONDAY","era":"CE","dayOfYear":135,"leapYear":false,"chronology":{"calendarType":"iso8601","id":"ISO"}}}
and similarly for the non-terminating Flux API:
jan#linux-6o1s:~/src> curl -s http://localhost:8080/events
data:{"id":1494887783347,"when":{"year":2017,"month":"MAY","monthValue":5,"dayOfMonth":15,"dayOfWeek":"MONDAY","era":"CE","dayOfYear":135,"leapYear":false,"chronology":{"calendarType":"iso8601","id":"ISO"}}}
data:{"id":1494887784348,"when":{"year":2017,"month":"MAY","monthValue":5,"dayOfMonth":15,"dayOfWeek":"MONDAY","era":"CE","dayOfYear":135,"leapYear":false,"chronology":{"calendarType":"iso8601","id":"ISO"}}}
data:{"id":1494887785347,"when":{"year":2017,"month":"MAY","monthValue":5,"dayOfMonth":15,"dayOfWeek":"MONDAY","era":"CE","dayOfYear":135,"leapYear":false,"chronology":{"calendarType":"iso8601","id":"ISO"}}}
...
The similarly trivial Angular 4 client with RxJS:
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit, OnDestroy {
title = 'app works!';
event: Observable<Event>;
subscription: Subscription;
constructor(
private _http: Http
) {
}
ngOnInit() {
this.subscription = this._http
.get("http://localhost:8080/events/322")
.map(response => response.json())
.subscribe(
e => {
this.event = e;
}
);
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
works fine for the Mono API:
"http://localhost:8080/events/322"
but the Flux API:
"http://localhost:8080/events"
never triggers the event handler, unlike curl.
Here's a working Angular 4 SSE example as Simon describes in his answer. This took a while to piece together so perhaps it'll be useful to others. The key piece here is Zone -- without Zone, the SSE updates won't trigger Angular's change detection.
import { Component, NgZone, OnInit, OnDestroy } from '#angular/core';
import { Http } from '#angular/http';
import { Observable } from 'rxjs/Observable';
import { BehaviorSubject } from 'rxjs/BehaviorSubject';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/map';
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit {
event: Observable<MyEvent>;
private _eventSource: EventSource;
private _events: BehaviorSubject<MyEvent> = new BehaviorSubject<MyEvent>(null);
constructor(private _http: Http, private _zone: NgZone) {}
ngOnInit() {
this._eventSource = this.createEventSource();
this.event = this.createEventObservable();
}
private createEventObservable(): Observable<MyEvent> {
return this._events.asObservable();
}
private createEventSource(): EventSource {
const eventSource = new EventSource('http://localhost:8080/events');
eventSource.onmessage = sse => {
const event: MyEvent = new MyEvent(JSON.parse(sse.data));
this._zone.run(()=>this._events.next(event));
};
eventSource.onerror = err => this._events.error(err);
return eventSource;
}
}
The corresponding HTML is simply:
<b>Observable of sse</b>
<div *ngIf="(event | async); let evt; else loading">
<div>ID: {{evt.id}} </div>
</div>
<ng-template #loading>Waiting...</ng-template>
The event is trivial:
export class MyEvent {
id: number;
when: any;
constructor(jsonData) {
Object.assign(this, jsonData);
}
}
and since my TS does not include EventSource or Callback, I stubbed them in:
interface Callback { (data: any): void; }
declare class EventSource {
onmessage: Callback;
onerror: Callback;
addEventListener(event: string, cb: Callback): void;
constructor(name: string);
close: () => void;
}
The Flux based controller is producing Server Sent Events (SSE). I don't think the Http client from Angular2 lets you consume SSE...
edit: looks like EventSource is what you need, see this similar question/answer: https://stackoverflow.com/a/36815231/1113486
Going to guess here that the url for /events is the problem because it should produce json to be handled.
#SpringBootApplication
#RestController
public class ReactiveServiceApplication {
#CrossOrigin
#GetMapping("/events/{id}")
public Mono<Event> eventById(#PathVariable long id) {
return Mono.just(new Event(id, LocalDate.now()));
}
#CrossOrigin
#GetMapping(value = "/events", produces = MediaType.APPLICATION_JSON_VALUE)
public Flux<Event> events() {
Flux<Event> eventFlux = Flux.fromStream(
Stream.generate(
()->new Event(System.currentTimeMillis(), LocalDate.now()))
);
Flux<Long> durationFlux = Flux.interval(Duration.ofSeconds(1));
return Flux.zip(eventFlux, durationFlux).map(Tuple2::getT1);
}
public static void main(String[] args) {
SpringApplication.run(ReactiveServiceApplication.class);
}
}
used jersey mvc and jsp, all requests to html or js files did through #Template or Viewable.
example;
#GET
#Path(JS_URL + "{type}")
#Template(name = "grid")
#Produces("application/javascript")
public Response buildJSGrid(#DefaultValue("") #PathParam("type") String type) {
Grid grid = new Grid(type);
....
return Response.ok(grid).build();
}
where grid is grid.jsp file with pure javascript inside
<%# page contentType="application/javascript;charset=UTF-8" language="java" %>
.....
also possible other variant with html and js, example;
#GET
#Path(FORM_URL + "{type}")
#Template(name = "form")
#Produces(MediaType.TEXT_HTML)
public Response buildAccountForm(#DefaultValue("") #PathParam("type") String type) {
Form form = new Form(type);
....
return Response.ok(form).build();
}
where form is form.jsp with html and js inside <script>..</script>
<%# page contentType="text/html;charset=UTF-8" language="java" %>
...
i need to minify result js and html/js before send to client, i try to use https://code.google.com/archive/p/htmlcompressor/ lib, but there need to pass String to htmlCompressor.compress(input);
tried use WriterInterceptor
public class MinifyJsInterceptor implements WriterInterceptor {
#Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
// here need to convert outputStream to InputStream and after to String ?
// result string to htmlCompressor.compress(resultString);
// after that convert result minify string back to resultOutputStream and set to context ?
context.setOutputStream(new GZIPOutputStream(resultOutputStream));
is it correct way ? and i can`t converts that outputstream to string
thanks
--update
answer to questions;
html + js mean that in some jsp are html markup and js code
<div id="form" style="width: 500px; display: none">
<div class="w2ui-page page-0">
<div class="w2ui-field">
</div>....
<script type="text/javascript">
var uiElement = (function () {
var config = {
onOpen: function (event) {
event.onComplete = function () {
$('#formContainer').w2render('form');
}
...
}());
</script>
on client that file requested by
$('#tempContainer').load('that file name - also dynamic', function (data, status, xhr) {
uiElement.init();
w2ui[layout].content(layout_main, w2ui[uiElement.name]);
});
And do you really return js-files in you resource methods?
some js and html + js files are dynamic build, example;
grid.jsp contains inside
<%# page contentType="application/javascript;charset=UTF-8" language="java" %>
var uiElement = (function () {
var config = {
grid: {
name: ${it.name},
listUrl:'${it.entityListUrl}',
formUrl:'${it.entityFormUrl}',
columns: ${it.columns},
records: ${it.records},
}}
there are ${it..} values from el expression and setting in resource method
#GET
#Path(JS_URL + "{type}")
#Template(name = "grid")
#Produces("application/javascript")
public Response buildJSGrid(#DefaultValue("") #PathParam("type") String type) {
Grid grid = new Grid(type);
....
return Response.ok(grid).build();
}}
and from client that js 'file' called by
$.getScript('dynamic js file name' - it is dynamic too).done(function (script, status, xhr) {
//console.log(xhr.responseText);
uiElement.init();
w2ui[layout].content(layout_main, w2ui[uiElement.name]);
});
also some html blocks build dynamic
{
<c:if test="${it.recid != 0}">
<div class="w2ui-field">
<label>active:</label>
<div>
<input name="active" type="checkbox"/>
</div>
</div>
</c:if>
}
-- update description,
grid builder;
one resource and one template for build any grid,
#GET
#Path(GRID + "{type}")
#Template(name = W2UI_VIEW_PREFIX + "grid/grid")
#Produces(MEDIA_TYPE_APPLICATION_JAVASCRIPT)
public Response buildGrid(#DefaultValue("") #PathParam("type") String type) {
for (W2UI ui : W2UI.values()) {
if (type.equals(ui.getName())) {
W2UIElement grid = ui.getUI();
return Response.ok(grid).build();
}
}
return Response.noContent().build();
}
also possible different templates(jsp files) through Viewable(template, model)
somewhere in menu builder for menu.jsp template
List<MenuItem> items..
MenuItem item1 = new MenuItem(W2UI.TASK_GRID, W2UIService.GRID);
items.add(item1);
where
W2UIService.GRID is string url for client js request and for server method resource #Path() anno.
and
public enum W2UI {
TASK_GRID("task_grid", "tasks", Type.SCRIPT){
#Override
public W2UIElement getUI() {
return new TaskGrid(getName());
}
},
.....
}
TaskGrid is filled model for grid.jsp template with js code, so easy to add any type of grid with different sets of data and buttons.
type of component(Type.SCRIPT) processing on the client by $.getScript(), Type.HTML by $('#tempContainer').load()
---update factory and providers;
#Provider
#Priority(200)
#HtmlMinify
public class HtmlMinifyInterceptor implements WriterInterceptor {
#Inject private HtmlCompressor compressor;
...
public class HtmlMinifierFactory implements Factory<HtmlCompressor> {
private HtmlCompressor compressor;
#Override
public HtmlCompressor provide() {
if (null == compressor) compressor = new HtmlCompressor();
ClosureJavaScriptCompressor jsCompressor = new ClosureJavaScriptCompressor();
jsCompressor.setCompilationLevel(CompilationLevel.SIMPLE_OPTIMIZATIONS);
..
#ApplicationPath("/")
public class MainRsConfig extends ResourceConfig {
public MainRsConfig() {
..
register(new AbstractBinder() {
#Override
protected void configure() {
bindFactory(HtmlMinifierFactory.class).to(HtmlCompressor.class).in(Singleton.class);
}
});
..
You can use a custom implementation of a ByteArrayOutputStream as a wrapper to the OutputStream of the WriterInterceptorContext:
import com.googlecode.htmlcompressor.compressor.Compressor;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
public class HtmlMinifyOutputStream extends ByteArrayOutputStream {
private OutputStream origOut;
private Compressor compressor;
public HtmlMinifyOutputStream(OutputStream origOut, Compressor compressor) {
this.origOut = origOut;
this.compressor = compressor;
}
public void close() throws IOException {
super.close();
String compressedBody = compressor.compress(new String(this.buf));
this.origOut.write(compressedBody.getBytes());
this.origOut.close();
}
}
The HtmlMinifyOutputStream can be used in the WriterInterceptor implementation. The HtmlCompressor instance is injected:
import com.googlecode.htmlcompressor.compressor.Compressor;
import javax.inject.Inject;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.ext.Provider;
import javax.ws.rs.ext.WriterInterceptor;
import javax.ws.rs.ext.WriterInterceptorContext;
import java.io.*;
#Provider
#HtmlMinify
public class MinifyHtmlInterceptor implements WriterInterceptor {
#Inject
private Compressor compressor;
#Override
public void aroundWriteTo(WriterInterceptorContext context) throws IOException, WebApplicationException {
final OutputStream outputStream = context.getOutputStream();
context.setOutputStream(new HtmlMinifyOutputStream(outputStream, compressor));
context.proceed();
}
}
#HtmlMinify is a NameBinding annotation, used to activate the MinifyHtmlInterceptor on specific resource methods. (see https://jersey.java.net/documentation/latest/filters-and-interceptors.html#d0e9988):
import javax.ws.rs.NameBinding;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
#NameBinding
#Retention(value = RetentionPolicy.RUNTIME)
public #interface HtmlMinify {}
The HtmlCompressor can be created only once per application and used concurrently, because:
HtmlCompressor and XmlCompressor classes are considered thread safe* and can be used in multi-thread environment (https://code.google.com/archive/p/htmlcompressor/)
Here is a HK2 factory (see: Implementing Custom Injection Provider) which creates the compressor instance and enables inline css and javascript compression:
import com.googlecode.htmlcompressor.compressor.Compressor;
import com.googlecode.htmlcompressor.compressor.HtmlCompressor;
import org.glassfish.hk2.api.Factory;
public class HtmlCompressorFactory implements Factory<Compressor> {
private HtmlCompressor compressor;
#Override
public Compressor provide() {
if(compressor == null) {
compressor = new HtmlCompressor();
}
compressor.setCompressJavaScript(true);
compressor.setCompressCss(true);
return compressor;
}
#Override
public void dispose(Compressor compressor) {}
}
The factory is registered with an AbstractBinder:
final ResourceConfig rc = new ResourceConfig().packages("com.example");
rc.register(new AbstractBinder() {
#Override
protected void configure() {
bindFactory(HtmlCompressorFactory.class).to(Compressor.class).in(Singleton.class);
}
});
If inline javascript or inline css compression is enabled:
HTML compressor with default settings doesn't require any dependencies. Inline CSS compression requires YUI compressor library.Inline JavaScript compression requires either YUI compressor library (by default) or Google Closure Compiler library. (https://code.google.com/archive/p/htmlcompressor/)
I use maven, so I added this dependency to my pom.xml:
<dependency>
<groupId>com.yahoo.platform.yui</groupId>
<artifactId>yuicompressor</artifactId>
<version>2.4.8</version>
</dependency>
If you want to use the Google Closure Compiler use this dependency:
<dependency>
<groupId>com.google.javascript</groupId>
<artifactId>closure-compiler</artifactId>
<version>r2388</version>
</dependency>
and activate it:
compressor.setJavaScriptCompressor(new ClosureJavaScriptCompressor());
compressor.setCompressJavaScript(true);
compressor.setCssCompressor(new YuiCssCompressor());
compressor.setCompressCss(true);
return compressor;
If you want to compress pure JavaScript or CSS files, you cannot use the htmlcompressor. This library supports only HTML files with inline CSS/JS. But you could implement a MinifyJsInterceptor or MinifyCssInterceptor analog to the MinifyHtmlInterceptor, which uses the YUI-Compressor and/or Google Closure libraries directly.
For gzip compression you should implement another interceptor. So it is possible to configure the minification and compression separately. If you activate multiple interceptors, use javax.annotation.Priority to controll the order of execution. (see: https://jersey.java.net/documentation/latest/filters-and-interceptors.html#d0e9927)
I am using Blueimp and server side is Java, Struts2. I couldn't find examples using Java, anyway I managed to use the sample code, but I am getting "Empty file upload result" when I am trying to upload a single file also. The HTML part is the same, I am not pasting here as it may go lengthy.
The jQuery is:
$(document).ready(function () {
'use strict';
// Initialize the jQuery File Upload widget:
$('#fileupload').fileupload();
// Enable iframe cross-domain access via redirect option:
$('#fileupload').fileupload(
'option',
'redirect',
window.location.href.replace(
/\/[^\/]*$/,
'/cors/result.html?%s'
)
);
if (window.location.hostname === 'blueimp.github.com') {
// Demo settings:
$('#fileupload').fileupload('option', {
url: '//jquery-file-upload.appspot.com/',
maxFileSize: 5000000,
acceptFileTypes: /(\.|\/)(gif|jpe?g|png)$/i,
process: [
{
action: 'load',
fileTypes: /^image\/(gif|jpeg|png)$/,
maxFileSize: 20000000 // 20MB
},
{
action: 'resize',
maxWidth: 1440,
maxHeight: 900
},
{
action: 'save'
}
]
});
// Upload server status check for browsers with CORS support:
if ($.support.cors) {
$.ajax({
url: '//jquery-file-upload.appspot.com/',
type: 'HEAD'
}).fail(function () {
$('<span class="alert alert-error"/>')
.text('Upload server currently unavailable - ' +
new Date())
.appendTo('#fileupload');
});
}
} else {
// Load existing files:
$('#fileupload').each(function () {
var that = this;
$.getJSON(this.action, function (result) {
if (result && result.length) {
$(that).fileupload('option', 'done')
.call(that, null, {result: result});
}
});
});
}
});
The action:
#Namespace("/")
#InterceptorRefs({
#InterceptorRef("fileUpload"),
#InterceptorRef("basicStack")
})
public class UploadAction extends ActionSupport implements ServletRequestAware, ServletResponseAware{
HttpServletRequest req;
HttpServletResponse res;
// private File fileUploadPath=new File("c:\\temp\\");
private List<File> uploads = new ArrayList<File>();
private List<String> uploadFileNames = new ArrayList<String>();
private List<String> uploadContentTypes = new ArrayList<String>();
public List<File> getUploads() {
return uploads;
}
public void setUploads(List<File> uploads) {
this.uploads = uploads;
}
public List<String> getUploadFileNames() {
return uploadFileNames;
}
public void setUploadFileNames(List<String> uploadFileNames) {
this.uploadFileNames = uploadFileNames;
}
public List<String> getUploadContentTypes() {
return uploadContentTypes;
}
public void setUploadContentTypes(List<String> uploadContentTypes) {
this.uploadContentTypes = uploadContentTypes;
}
#Action(value="upload", results = { #Result(name="success", type="json")
})
public String uploadFiles() throws IOException
{
System.out.println("upload1");
System.out.println("files:");
for (File u: uploads) {
System.out.println("*** "+u+"\t"+u.length());
}
System.out.println("filenames:");
for (String n: uploadFileNames) {
System.out.println("*** "+n);
}
System.out.println("content types:");
for (String c: uploadContentTypes) {
System.out.println("*** "+c);
}
System.out.println("\n\n");
if (!ServletFileUpload.isMultipartContent(req)) {
throw new IllegalArgumentException("Request is not multipart, please 'multipart/form-data' enctype for your form.");
}
return SUCCESS;
}
#Override
public void setServletRequest(HttpServletRequest hsr) {
this.req=hsr;
}
#Override
public void setServletResponse(HttpServletResponse hsr) {
this.res=hsr;
}
}
As I said, I have changed the action file, but I still get all empty values for files, and in the Firebug's GET response I see "Request is not multipart, please 'multipart/form-data' enctype for your form".
You may use fileUpload interceptor to parse your "multipart/form-data" requests. It uses the same commons-fileupload implementation wrapped by the MultipartRequestWrapper in prepare operations by the Struts2 dispatcher. More about how to file upload with examples you could find here.