Olá, Lucas, obrigado pela resposta!
Depois de lutar um pouco consegui chegar a uma solução que em um primeiro momento me pareceu satisfatória.
Quais são os problemas:
- Avisar ao usuario que o tamanho do arquivo que ele tentou mandar excedeu o limite
- Quando o usuario envia um arquivo com tamanho maior que o limite o processamento continua sem que os campos do form sejam processados, passando argumentos null para o controller, mesmo quando esses campos foram preenchidos.
A ação do meu form invoca um método semelhante a esse:
public void salvar(String msg, UploadedFile anexo){
...
}
Se o anexo for maior que o limite, os dois campos recebidos no método são null, mesmo que “msg” tenha sido preenchido no form.
No meu caso, seria bom receber o valor do campo msg mesmo que o anexo seja maior que o limite. Nesse caso, seria bom receber um UploadedFile que pudesse me indicar de alguma maneira que ele excedeu o limite, como o código abaixo (chamei todas as minhas classes de CustomXXX por falta de criatividade ;). Os códigos completos que fiz estão em anexo) :
CustomUploadedFile.java
public class CustomUploadedFile implements UploadedFile{
private boolean sizeLimitExceeded;
private UploadedFile delegate;
public CustomUploadedFile(UploadedFile delegate, boolean sizeLimitExceeded) {
this.delegate = delegate;
this.sizeLimitExceeded = sizeLimitExceeded;
}
@Override
public String getContentType() {
return delegate.getContentType();
}
@Override
public InputStream getFile() {
return delegate.getFile();
}
@Override
public String getFileName() {
return delegate.getFileName();
}
public boolean wasSizeLimitExceeded() {
return sizeLimitExceeded;
}
}
Essa classe possui uma indicação de que o tamanho do anexo excedeu o limite permitido.
Para que essa classe seja recebida pelo controller, criei as classes abaixo:
CustomMultipartController.java
@Component
@RequestScoped
public class CustomMultipartInterceptor extends MultipartInterceptor{
.... removi o codigo que vinha aqui para melhorar o processamento do javascript ao acessar a thread no forum (ver codigo completo no arquivo em anexo) ....
@SuppressWarnings("unchecked")
public void intercept(InterceptorStack stack, ResourceMethod method, Object instance) throws InterceptionException {
logger.debug("Trying to parse multipart request.");
File temporaryDirectory = config.getDirectory();
FileItemFactory factory = createFactoryForDiskBasedFileItems(temporaryDirectory);
List<CustomFileItem> fileItems = new ArrayList<CustomFileItem>();
// Classe CustomServletFileUpload tem como caracteristica retornar uma lista de
// CustomFileItem. CustomFileItem tem metodo isSizeLimitExceeded usado na validacao nos controllers
// para determinar o comportamento a ser tomado se o arquivo tem tamanho > limite...
//
CustomServletFileUpload upload = new CustomServletFileUpload(factory);
// alterei setSizeMax (tamanho maximo da requisicao) por setFileSizeMax (tamanho maximo do arquivo).
// Isso porque se existirem 2 arquivos a serem anexados em um form (dois inputs tipo "file"), quero
// que cada um deles tenha o tamanho maximo de sizeLimit. Se eu usasse setSizeMax, a soma do tamanho
// dos dois arquivos precisaria ser < sizeLimit
upload.setFileSizeMax(sizeLimit);
try {
fileItems = upload.parseRequest(request);
} catch (FileUploadException e) {
logger.warn("There was some problem parsing this multipart request, or someone is not sending a RFC1867 compatible multipart request.", e);
stack.next(method, instance);
return;
}
// Cria CustomUploadedFile (classe que tem informacao se o upload ultrapassou o limite)
new CustomMultipartItemsProcessor(fileItems, request, parameters).process();
stack.next(method, instance);
// TODO should we delete the temporary files afterwards or onExit as
// done by now? maybe also a config in .properties
}
.... removi o codigo que vinha aqui para melhorar o processamento do javascript ao acessar a thread no forum (ver codigo completo no arquivo em anexo) ....
}
CustomFileItem.java
public class CustomFileItem implements FileItem{
private static final long serialVersionUID = 1L;
private FileItem delegate;
private boolean sizeLimitExceeded;
public CustomFileItem(FileItem delegate) {
this.delegate = delegate;
}
.... removi o codigo que vinha aqui para melhorar o processamento do javascript ao acessar a thread no forum (ver codigo completo no arquivo em anexo) ....
public void setSizeLimitExceeded(boolean b) {
this.sizeLimitExceeded = b;
}
public boolean wasSizeLimitExceeded(){
return sizeLimitExceeded;
}
}
CustomServletFileUpload.java (importante colocar essa classe no pacote org.apache.commons.fileupload para ter acesso à classe MultipartStream.ProgressNotifier)
OBS: CustomFileItemIteratorImpl é uma cópia completa da classe FileItemIteratorImpl do apache commons upload. Copiei ela porque queria comentar o trecho de código “itemStream.close(true);” no método “raiseError” de “LimitedInputStream” na construtora de FileItemStreamImpl… Quando ocorre um FileSizeLimitExceededException, o inputStream é fechado, não permitindo que os demais campos do formulario sejam processados…
public class CustomServletFileUpload extends ServletFileUpload{
public CustomServletFileUpload() {
super();
}
public CustomServletFileUpload(FileItemFactory fileItemFactory) {
super(fileItemFactory);
}
@Override
public List /*CustomFileItem*/ parseRequest(HttpServletRequest request) throws FileUploadException {
return parseRequest(new ServletRequestContext(request));
}
@Override
public List /*CustomFileItem*/ parseRequest(RequestContext ctx) throws FileUploadException {
try {
FileItemIterator iter = getItemIterator(ctx);
List items = new ArrayList();
FileItemFactory fac = getFileItemFactory();
if (fac == null) {
throw new NullPointerException("No FileItemFactory has been set.");
}
while (iter.hasNext()) {
FileItemStream item = iter.next();
CustomFileItem fileItem = new CustomFileItem(fac.createItem(item.getFieldName(),
item.getContentType(), item.isFormField(),
item.getName()));
try {
Streams.copy(item.openStream(), fileItem.getOutputStream(), true);
} catch (FileUploadIOException e) {
// caso a excecao seja de tamanho limite excedido, configura a flag
// de tamanho excedido no item e continua o processamento dos demais
// parametros do request
if(e.getCause() instanceof FileSizeLimitExceededException){
FileSizeLimitExceededException exception = (FileSizeLimitExceededException) e.getCause();
// se quiser obter o tamanho maximo lido: exception.getActualSize()
fileItem.setSizeLimitExceeded(true);
}
else{
throw (FileUploadException) e.getCause();
}
} catch (IOException e) {
throw new IOFileUploadException(
"Processing of " + MULTIPART_FORM_DATA
+ " request failed. " + e.getMessage(), e);
}
if (fileItem instanceof FileItemHeadersSupport) {
final FileItemHeaders fih = item.getHeaders();
((FileItemHeadersSupport) fileItem).setHeaders(fih);
}
items.add(fileItem);
}
return items;
} catch (FileUploadIOException e) {
throw (FileUploadException) e.getCause();
} catch (IOException e) {
throw new FileUploadException(e.getMessage(), e);
}
}
.... removi o codigo que vinha aqui para melhorar o processamento do javascript ao acessar a thread no forum (ver codigo completo no arquivo em anexo) ....
CustomMultipartItemsProcessor.java
/**
* Copiado de br.com.caelum.vraptor.interceptor.multipart.MultipartItemsProcessor,
* mas ao inves de montar UploadedFile, monta CustomUploadedFile (que contem indicacao de que o
* upload foi excedido)
* */
public class CustomMultipartItemsProcessor {
private static final Logger logger = LoggerFactory.getLogger(CustomMultipartItemsProcessor.class);
private final List<CustomFileItem> items;
private final HttpServletRequest request;
private final MutableRequest parameters;
public CustomMultipartItemsProcessor(List<CustomFileItem> items, HttpServletRequest request, MutableRequest parameters) {
this.items = items;
this.request = request;
this.parameters = parameters;
}
public void process() {
Multimap<String, String> params = LinkedListMultimap.create();
for (CustomFileItem item : items) {
if (item.isFormField()) {
params.put(item.getFieldName(), item.getString());
continue;
}
if (notEmpty(item)) {
try {
CustomUploadedFile fileInformation = new CustomUploadedFile(new DefaultUploadedFile(item.getInputStream(), item.getName(), item.getContentType()), item.wasSizeLimitExceeded());
parameters.setParameter(item.getFieldName(), item.getName());
request.setAttribute(item.getName(), fileInformation);
logger.debug("Uploaded file: " + item.getFieldName() + " with " + fileInformation);
} catch (Exception e) {
throw new InvalidParameterException("Cant parse uploaded file " + item.getName(), e);
}
} else {
logger.debug("A file field was empty: " + item.getFieldName());
}
}
for (String paramName : params.keySet()) {
Collection<String> paramValues = params.get(paramName);
parameters.setParameter(paramName, paramValues.toArray(new String[paramValues.size()]));
}
}
private static boolean notEmpty(FileItem item) {
return !item.getName().trim().equals("");
}
}
Bom, depois disso tudo, altero meu controller anterior para ficar como abaixo:
public void salvar(String msg, CustomUploadedFile anexo){
if(anexo.wasSizeLimitExceeded()){
result.include("msg", msg);
result.include("erros", new String[]{"O tamanho do arquivo a ser anexado deve ser menor que XXX MB"});
return;
}
...
}
Desse modo o form é exibido novamente exibindo a mensagem de erro e com o campo msg devidamente populado com o valor submetido.
Pelo que pude testar hoje funcionou como eu queria, apesar de parecer xunxado 
Viajei na maionese?