Thanks in advance for your support.
Currently I´m stuck in the next problem. I developed an Aspect class to validate my input JSON from al the pkg of RestController.
Complying with certain characteristics.
Each method of my controllers returns a different DTO object.
I created a new generic object to return it from my aspect, when my logic is not fulfilled. When I do tests, I get an error of CannotCastClass "xxxxDTO" to newErrorResponseDTO.
Currently I already can obtain the method signature or the object type. My idea is to cast the return type (from methodSignature) to my new DTOResponse. The object response is always different.
I mention that the architecture and design of the total project was already developed. I only did the aspect
At the moment, I have not succeeded.
I attach evidence. Thanks
I tried ResponseAdvice, and multiple ways to cast objects.
I prefer to stay in the aspect. I get the solution changing all the response DTO in controller to Object generic. Asumming that doing is bad practice, i prefer real solution
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
// Other imports missing...
#Aspect
#Component("validateParameterAspect")
public class ValidatorParameterAspect {
public static final Logger logger = Logger.getLogger(ValidatorParameterAspect.class);
#Autowired
ServiciosRest servicio;
#Pointcut("execution(* com.actinver.rest.*.* (..))")
public void executeController() {}
#Pointcut("#annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void logRequestMapping() {}
#Around("logRequestMapping() && executeController() && args(..,#RequestBody requestBody) ")
public Object logRequestBody(ProceedingJoinPoint joinPoint, Object requestBody) throws Throwable {
String vlDataDecrypt = "";
try {
// output = joinPoint.proceed();
System.out.println("--------------123------------");
logger.warn("Entering in Method : " + joinPoint.getSignature().getName());
logger.warn("Class Name : " + joinPoint.getSignature().getDeclaringTypeName());
logger.warn("Arguments : " + Arrays.toString(joinPoint.getArgs()));
logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());
SimpleJSONDataContainer args = (SimpleJSONDataContainer) joinPoint.getArgs()[0];
MethodSignature sign = (MethodSignature) joinPoint.getSignature();
Class<?> ret = sign.getReturnType();
String returnString = sign.getReturnType().getName();
logger.warn("Signature : " + ret);
vlDataDecrypt = AESHelper.decrypt(servicio.getSeedWord(), args.getData());
logger.info(" Decrypt -> " + vlDataDecrypt);
logger.info("args " + args.getData());
ErrorDataResponse res = validDataEmpty(args.getData());
if (res.getResult() == "2") {
return res; // or cast Class<?>
//return ret.cast(res);
}
} catch (Exception e) {
logger.error("Stack trace -> ", e);
}
return joinPoint.proceed();
}
public ErrorDataResponse validDataEmpty(String vlDataDecrypt) {
ErrorDataResponse errorDto = new ErrorDataResponse();
if (vlDataDecrypt == null || vlDataDecrypt.hashCode() == "77631826690E45839D7B49B932CBC81B".hashCode()
&& vlDataDecrypt.equalsIgnoreCase("77631826690E45839D7B49B932CBC81B")) {
errorDto.setResult("2");
errorDto.setMensaje(RestValidatorUtil.EnumErrors.ERROR_INPUT.getMsg());
logger.info("JSON null" + errorDto.getResult());
return errorDto;
}
return errorDto;
}
}
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
// Other imports missing...
#RestController
#RequestMapping("inicio")
public class Bursanet {
public final static Logger logger = Logger.getLogger(Bursanet.class);
#RequestMapping(
value = "cashByDate",
method = { RequestMethod.GET, RequestMethod.POST },
consumes = "application/json",
produces = "application/json"
)
public CashByDateDTO cashByDate(
#RequestBody SimpleJSONDataContainer simpleJSONDataContainer,
Authentication authentication
) {
String vlDataDecrypt = "";
CashByDateDTO outJson = new CashByDateDTO();
CashByDateRequest request = null;
try {
UsernamePasswordAuthenticationToken userPasswordAuthenticationToken =
(UsernamePasswordAuthenticationToken)
((OAuth2Authentication) authentication).getUserAuthentication();
//////example
return outJson;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
It is very difficult to analyse your code because you are not providing an MCVE:
There are no package names in your classes.
There are no imports either.
You use several project-specific classes (not part of the Spring Framework) the code of which you also don't share here.
There is no Spring configuration either.
So I have to make some educated guesses here. From what I can see, I can tell you this:
If you expect ValidatorParameterAspect.logRequestBody(..) to intercept execution of Bursanet.cashByDate(..), it should not work because
in args(.., #RequestBody requestBody) you are expecting that parameter to be the last one in the target method's signature, but actually in Bursanet.cashByDate(..) it is the first one. So the pointcut should never match.
Again in args(.., #RequestBody requestBody) you ought to use a fully qualified class name, i.e. args(.., #org.springframework.web.bind.annotation.RequestBody requestBody).
Please also note that execution(* com.actinver.rest.*.* (..)) only matches methods in classes residing directly in the com.actinver.rest package, not in any subpackages. If you want to include those too, you need to change the pointcut to execution(* com.actinver.rest..* (..)).
In your question you mention you only want to intercept REST controllers, but you do not limit pointcut matching to classes with a #RestController annotation. You could do that via #within(org.springframework.web.bind.annotation.RestController). Right now you are doing it indirectly by only relying on methods with #annotation(org.springframework.web.bind.annotation.RequestMapping), which will also work as long as those methods only occur in #RequestController classes. Probably this is the case in your application, I am just mentioning it as a detail.
Instead of SimpleJSONDataContainer args = (SimpleJSONDataContainer) joinPoint.getArgs()[0];, why don't you bind the first argument to a SimpleJSONDataContainer parameter via args() and then just use the currently unused requestBody advice method parameter in your code? Something like this:
#Around("logRequestMapping() && executeController() && args(#org.springframework.web.bind.annotation.RequestBody requestBody, ..)")
public Object logRequestBody(ProceedingJoinPoint joinPoint, SimpleJSONDataContainer requestBody) throws Throwable {
// (...)
vlDataDecrypt = AESHelper.decrypt(servicio.getSeedWord(), requestBody.getData());
logger.info(" Decrypt -> " + vlDataDecrypt);
logger.info("args " + requestBody.getData());
ErrorDataResponse res = validDataEmpty(requestBody.getData());
// (...)
}
You define MethodSignature sign = (MethodSignature) joinPoint.getSignature(); but don't use it above several times where you repeatedly call joinPoint.getSignature(), too. Instead you could just reorganise the code like this:
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
System.out.println("--------------123------------");
logger.warn("Entering in Method : " + methodSignature.getName());
logger.warn("Class Name : " + methodSignature.getDeclaringTypeName());
logger.warn("Arguments : " + Arrays.toString(joinPoint.getArgs()));
logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());
Class<?> ret = methodSignature.getReturnType();
String returnString = methodSignature.getReturnType().getName();
I never understood why so many people call many JoinPoint methods in order to extract details for logging if instead they could simply log the joinpoint instance. This would show the type of pointcut (e.g. execution()) as well as the target method signature. Okay, if you want to list all method arguments, you can do this additionally, but how about this, wouldn't that be enough?
logger.warn(joinPoint);
// logger.warn("Entering in Method : " + methodSignature.getName());
// logger.warn("Class Name : " + methodSignature.getDeclaringTypeName());
logger.warn("Arguments : " + Arrays.toString(joinPoint.getArgs()));
// logger.warn("Target class : " + joinPoint.getTarget().getClass().getName());
This whole code block I guess you can also remove. It even prints wrong information and calls the return type "signature":
Class<?> ret = methodSignature.getReturnType();
String returnString = methodSignature.getReturnType().getName();
logger.warn("Signature : " + ret);
Now for the part which is probably your problem:
ErrorDataResponse res = validDataEmpty(requestBody.getData());
if (res.getResult() == "2") {
return res; // or cast Class<?>
//return ret.cast(res);
}
Here you are making the aspect advice skip the joinPoint.proceed() call and return another object instead. The method you intercept has the signature public CashByDateDTO cashByDate(..), i.e. it returns a specific DTO type. If you want to return an ErrorDataResponse instead, this would only work if ErrorDataResponse was a subtype of CashByDateDTO, which probably it is not. From the class names I would even say that a *Response and a *DTO are completely different object types. Your advice cannot just change or ignore the method signature. You have to return a CashByDateDTO object, no matter what. If you cannot do that here, maybe you are intercepting the wrong method or trying to do the wrong thing in your aspect.
Sorry for the lengthy reply, but there is so much chaos in your code, I had to point out some details.
Related
I find the same question in there but didn`t find a useful answer, so I support more details. My code is the following.
#Target(ElementType.TYPE)
#Retention(RetentionPolicy.RUNTIME)
public #interface DS {
String value();
}
public class AnnotationAspect {
#Around("#target(com.yh.application.DS)")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
String dsName = getDataSourceAnnotation(joinPoint).value();
System.out.println("enter in aspect:" + dsName);
return joinPoint.proceed();
}
here is a demo,
just run the application you can see the error stack trace
Unable to proxy interface-implementing method
[public final void org.springframework.boot.web.servlet.RegistrationBean.onStartup
(javax.servlet.ServletContext) throws javax.servlet.ServletException]
because it is marked as final: Consider using interface-based JDK proxies instead!
seems I need to change the aop proxy type to JDK, but when I did this, another error is prompted.
The bean 'dispatcherServlet' could not be injected as a 'org.springframework.web.servlet.DispatcherServlet' because it is a JDK dynamic proxy
Does anyone help me? thank you!
R.G's solution is correct, you ought to limit the pointcut scope. BTW, looking at your aspect code, I noticed this contrived way of getting the annotation value:
private DS getDataSourceAnnotation(ProceedingJoinPoint joinPoint) {
Class<?> targetClass = joinPoint.getTarget().getClass();
DS dsAnnotation = targetClass.getAnnotation(DS.class);
if (Objects.nonNull(dsAnnotation)) {
return dsAnnotation;
}
else {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
return methodSignature.getMethod().getAnnotation(DS.class);
}
}
I suggest you just bind the annotation to an advice method parameter like this:
package com.yh.application;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
#Aspect
#Component
public class AnnotationAspect {
#Before("#target(ds) && within(com.yh..*)")
public void interceptDS(JoinPoint joinPoint, DS ds) {
System.out.println(joinPoint + " -> DS value = " + ds.value());
}
}
Update:
I forgot to explain why you were getting the error in the first place: Pointcuts like this(), target(), #this(), #target() can only be determined dynamically during runtime because they access active object instances. Hence, all possible Spring components (also internal ones) are being aspect-woven, which is also the reason why the workaround to limit the aspect scope by using statically evaluated pointcut designators like within() help you avoid the problem.
But actually, using a statically evaluated pointcut designator in the first place, if it is a viable alternative, is the best idea. It is also faster than weaving the world, creating dozens or hundreds of proxies, and then to dynamically evaluate pointcuts over and over again. Luckily, in this case such an alternative exists: #within().
#Aspect
#Component
public class AnnotationAspect {
#Before("#within(ds)")
public void interceptDS(JoinPoint joinPoint, DS ds) {
System.out.println(joinPoint + " -> DS value = " + ds.value());
}
}
I have such aspect-based logging:
#Pointcut("#annotation(Loggable)")
public void loggableAnnotation() {}
#Around("loggableAnnotation()")
public Object simpleProcess(ProceedingJoinPoint joinPoint) throws Throwable {
return this.processWithBody(joinPoint, null);
}
#Around("loggableAnnotation() && args(#org.springframework.web.bind.annotation.RequestBody body,..)")
public Object processWithBody(ProceedingJoinPoint joinPoint, Object body) throws Throwable {
// do things
}
And it works fine when I perform request with #RequestBody, advice processWithBody() is triggered. But when I perform request that doesn't have a #RequestBody (only #PathVariable and #RequestParam) the simpleProcess() is not triggered, instead in the processWithBody() I receive path variable value as the body parameter.
Why does it happen and how do I pocess two types of requests differently (in the same advice if possible)?
You are making three basic miskates:
You are trying to match parameter argument annotations from within args(), but there it has no effect, which is why processWithBody(..) matches an unwanted parameter and binds it to body. It should be transferred to an execution() pointcut.
Your pointcut syntax is wrong, even if you transfer it to execution(), i.e. something likeexecution(* *(#org.springframework.web.bind.annotation.RequestBody *, ..)) would match if the type(!) of the parameter has a #RequestBody annotation, not the parameter itself.In order to achieve that you need to put the parameter itself into parentheses like (*), i.e. execution(* *(#org.springframework.web.bind.annotation.RequestBody (*), ..)).
You have to make sure that the pointcuts are mutually exclusive, otherwise multiple advices shall match on the same joinpoint. To be precise, you need to differentiate between the following cases:
methods annotated by #Loggable with a first method parameter annotated by #RequestBody
methods annotated by #Loggable with a first method parameter not annotated by #RequestBody
methods annotated by #Loggable without any parameters
Here is an example in plain Java + AspectJ (no Spring or Spring AOP), but the aspect syntax should be identical in Spring AOP:
Annotation + driver application:
package de.scrum_master.app;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
#Retention(RUNTIME)
#Target(METHOD)
public #interface Loggable {}
package de.scrum_master.app;
import org.springframework.web.bind.annotation.RequestBody;
public class Application {
public static void main(String[] args) {
Application application = new Application();
application.doNotLogMe("foo", 11);
application.doNotLogMeEither();
application.doNotLogMeEither("foo", 11);
application.logMe("foo", 11);
application.logMeToo("foo", 11);
application.logMeToo();
}
public void doNotLogMe(#RequestBody String body, int number) {}
public void doNotLogMeEither() {}
public void doNotLogMeEither(String body, int number) {}
#Loggable public void logMe(#RequestBody String body, int number) {}
#Loggable public void logMeToo(String body, int number) {}
#Loggable public void logMeToo() {}
}
Aspect:
As you can see, I am using the differentiating the three cases mentioned above and also satisfy your need for a common helper method which I called logIt(..). There you can put all the complex logging stuff you want to use without having any duplicate code in your advice methods.
package de.scrum_master.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
#Aspect
public class MyAspect {
#Pointcut("#annotation(de.scrum_master.app.Loggable)")
public void loggableAnnotation() {}
#Around(
"loggableAnnotation() && " +
"execution(* *())"
)
public Object simpleProcessWithoutParameters(ProceedingJoinPoint joinPoint) throws Throwable {
return logIt(joinPoint, null);
}
#Around(
"loggableAnnotation() && " +
"execution(* *(!#org.springframework.web.bind.annotation.RequestBody (*), ..))"
)
public Object simpleProcessWithParameters(ProceedingJoinPoint joinPoint) throws Throwable {
return logIt(joinPoint, null);
}
#Around(
"loggableAnnotation() && " +
"execution(* *(#org.springframework.web.bind.annotation.RequestBody (*), ..)) && " +
"args(body, ..)"
)
public Object processWithBody(ProceedingJoinPoint joinPoint, Object body) throws Throwable {
return logIt(joinPoint, body);
}
private Object logIt(ProceedingJoinPoint joinPoint, Object body) throws Throwable {
System.out.println(joinPoint + " -> " + body);
return joinPoint.proceed();
}
}
Console log:
execution(void de.scrum_master.app.Application.logMe(String, int)) -> foo
execution(void de.scrum_master.app.Application.logMeToo(String, int)) -> null
execution(void de.scrum_master.app.Application.logMeToo()) -> null
P.S.: The difference between execution(* *(#MyAnn *)) and execution(* *(#MyAnn (*))) is subtle and thus tricky. Unfortunately, it is not properly documented here where it should be. To be precise, the latter case is not documented at all, only maybe in some AspectJ release notes and of course in unit tests. But no normal user would look there.
I want to log Controller and the rest of packages differently. I know I can use 2 separate methods for this but these 2 methods are very similar, so I want to add a code to check that would look something like this
#Around("controllerPoint() || theRest()")
public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
if( called from controllerPoint() ) {
execute this short section of code # (1)
}
// rest of code
What would this code be like?
Also, if after I execute (1) and I want to pass a variable to this same method again when it executes for other packages, how can I do it?
you can invoke the method like the below which will return your method name
joinPoint.getSignature().getName()
You could get method name, from join point:
#Aspect
#Configuration
public class TrackingConfig {
#Around("execution(* your.package.Controller.*(..))")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
if ("theRest".equals(methodName)) {
System.out.println("AROUND! theRest ");
} else if ("controllerPoint".equals(methodName)) {
System.out.println("AROUND! controllerPoint ");
}
return pjp.proceed();
}
}
using Spring AOP, I'm trying to put logging in my web application for an object called corelation like below :-
LoggingCorrelationEnrichingAspect.java:-
#Aspect
#Component
public class LoggingCorrelationEnrichingAspect {
private static final Logger logger = getLogger(LoggingCorrelationEnrichingAspect.class);
#Around("#annotation(Correlated)")
public Object wrapWithCorrelationContext(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
logger.info("Entering "+ proceedingJoinPoint.getSignature().getName() +" with Correlation Id:: "
+ ((Map)proceedingJoinPoint.getArgs()[0]).get(CommerceConnectorConstants.HttpHeaders.CORRELATION_ID).get());
return ((Mono<?>) proceedingJoinPoint.proceed());
}
}
Correlated.java:-
#Inherited
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
public #interface Correlated {}
In my main REST Controller operation, using #Correlated annotation, I'm trying to log this corellation like below :-
#Correlated
#GetMapping(path = "/products}", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Mono<ProductBeanResponse> getProducts(
#RequestHeader(name = Test.HttpHeaders.TENANT_ID, required = true) UUID tId,
#RequestHeader(name = Test.HttpHeaders.CORRELATION_ID, required = true) UUID correlationId
----
---
}
However, when I test my service using PostMan tool and see the applicaiton logs, the corelation id is never logged :-
logger.info("Entering "+ proceedingJoinPoint.getSignature().getName() +" with Correlation Id:: "
+ ((Map)proceedingJoinPoint.getArgs()[0]).get(CommerceConnectorConstants.HttpHeaders.CORRELATION_ID).get());
Please advise is this a configuration issue in Spring AOP.
Thanks
This can get working in either of below two ways
Provide fully qualified name of Correlated in the pointcut definition as #Around("#annotation(com.x.y.z.Correlated)")
Update the Aspect method signature to include the Correlated as second argument
#Around("#annotation(correlated)")
public Object wrapWithCorrelationContext(ProceedingJoinPoint proceedingJoinPoint, Correlated correlated ) throws Throwable {
logger.info("Entering "+ proceedingJoinPoint.getSignature().getName() +" with Correlation Id:: "
+ ((Map)proceedingJoinPoint.getArgs()[0]).get(CommerceConnectorConstants.HttpHeaders.CORRELATION_ID).get());
return ((Mono<?>) proceedingJoinPoint.proceed());
}
Let know in comments if anything else is required.
P.S.: Also as pointed out by M. Deinum make sure to remove object cast.
Important : This question is completely useless for any Spring version higher than 3.0.4 as the issue discussed in this thread had been fixed in that version a long ago and is no longer reproducible in subsequent versions of Spring.
I'm using Spring version 3.0.2. I need to upload multiple files using the multiple="multiple" attribute of a file browser such as,
<input type="file" id="myFile" name="myFile" multiple="multiple"/>
(and not using multiple file browsers something like the one stated by this answer, it indeed works I tried).
Although no versions of Internet Explorer supports this approach unless an appropriate jQuery plugin/widget is used, I don't care about it right now (since most other browsers support this).
This works fine with commons fileupload but in addition to using RequestMethod.POST and RequestMethod.GET methods, I also want to use other request methods supported and suggested by Spring like RequestMethod.PUT and RequestMethod.DELETE in their own appropriate places. For this to be so, I have configured Spring with HiddenHttpMethodFilter which goes fine as this question indicates.
but it can upload only one file at a time even though multiple files in the file browser are chosen. In the Spring controller class, a method is mapped as follows.
#RequestMapping(method={RequestMethod.POST}, value={"admin_side/Temp"})
public String onSubmit(#RequestParam("myFile") List<MultipartFile> files, #ModelAttribute("tempBean") TempBean tempBean, BindingResult error, Map model, HttpServletRequest request, HttpServletResponse response) throws IOException, FileUploadException {
for (MultipartFile file : files) {
System.out.println(file.getOriginalFilename());
}
}
Even with the request parameter #RequestParam("myFile") List<MultipartFile> files which is a List of type MultipartFile (it can always have only one file at a time).
I could find a strategy which is likely to work with multiple files on this blog. I have gone through it carefully.
The solution below the section SOLUTION 2 – USE THE RAW REQUEST says,
If however the client insists on using the same form input name such
as ‘files[]‘ or ‘files’ and then populating that name with multiple
files then a small hack is necessary as follows. As noted above Spring
2.5 throws an exception if it detects the same form input name of type file more than once. CommonsFileUploadSupport – the class which throws
that exception is not final and the method which throws that exception
is protected so using the wonders of inheritance and subclassing one
can simply fix/modify the logic a little bit as follows. The change
I’ve made is literally one word representing one method invocation
which enables us to have multiple files incoming under the same form
input name.
It attempts to override the method
protected MultipartParsingResult parseFileItems(List fileItems, String encoding){}
of the abstract class CommonsFileUploadSupport by extending the class CommonsMultipartResolver such as,
package multipartResolver;
import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletContext;
import org.apache.commons.fileupload.FileItem;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
public final class MultiCommonsMultipartResolver extends CommonsMultipartResolver {
public MultiCommonsMultipartResolver() {}
public MultiCommonsMultipartResolver(ServletContext servletContext) {
super(servletContext);
}
#Override
#SuppressWarnings("unchecked")
protected MultipartParsingResult parseFileItems(List fileItems, String encoding) {
Map<String, MultipartFile> multipartFiles = new HashMap<String, MultipartFile>();
Map multipartParameters = new HashMap();
// Extract multipart files and multipart parameters.
for (Iterator it = fileItems.iterator(); it.hasNext();) {
FileItem fileItem = (FileItem) it.next();
if (fileItem.isFormField()) {
String value = null;
if (encoding != null) {
try {
value = fileItem.getString(encoding);
} catch (UnsupportedEncodingException ex) {
if (logger.isWarnEnabled()) {
logger.warn("Could not decode multipart item '" + fileItem.getFieldName()
+ "' with encoding '" + encoding + "': using platform default");
}
value = fileItem.getString();
}
} else {
value = fileItem.getString();
}
String[] curParam = (String[]) multipartParameters.get(fileItem.getFieldName());
if (curParam == null) {
// simple form field
multipartParameters.put(fileItem.getFieldName(), new String[]{value});
} else {
// array of simple form fields
String[] newParam = StringUtils.addStringToArray(curParam, value);
multipartParameters.put(fileItem.getFieldName(), newParam);
}
} else {
// multipart file field
CommonsMultipartFile file = new CommonsMultipartFile(fileItem);
if (multipartFiles.put(fileItem.getName(), file) != null) {
throw new MultipartException("Multiple files for field name [" + file.getName()
+ "] found - not supported by MultipartResolver");
}
if (logger.isDebugEnabled()) {
logger.debug("Found multipart file [" + file.getName() + "] of size " + file.getSize()
+ " bytes with original filename [" + file.getOriginalFilename() + "], stored "
+ file.getStorageDescription());
}
}
}
return new MultipartParsingResult(multipartFiles, multipartParameters);
}
}
What happens is that the last line in the method parseFileItems() (the return statement) i.e.
return new MultipartParsingResult(multipartFiles, multipartParameters);
causes a compile-time error because the first parameter multipartFiles is a type of Map implemented by HashMap but in reality, it requires a parameter of type MultiValueMap<String, MultipartFile>
It is a constructor of a static class inside the abstract class CommonsFileUploadSupport,
public abstract class CommonsFileUploadSupport {
protected static class MultipartParsingResult {
public MultipartParsingResult(MultiValueMap<String, MultipartFile> mpFiles, Map<String, String[]> mpParams) {}
}
}
The reason might be - this solution is about the Spring version 2.5 and I'm using the Spring version 3.0.2 which might be inappropriate for this version.
I however tried to replace the Map with MultiValueMap in various ways such as the one shown in the following segment of code,
MultiValueMap<String, MultipartFile>mul=new LinkedMultiValueMap<String, MultipartFile>();
for(Entry<String, MultipartFile>entry:multipartFiles.entrySet()) {
mul.add(entry.getKey(), entry.getValue());
}
return new MultipartParsingResult(mul, multipartParameters);
but no success. I'm not sure how to replace Map with MultiValueMap and even doing so could work either. After doing this, the browser shows the Http response,
HTTP Status 400 -
type Status report
message
description The request sent by the client was syntactically incorrect
().
Apache Tomcat/6.0.26
I have tried to shorten the question as possible as I could and I haven't included unnecessary code.
How could be made it possible to upload multiple files after Spring has been configured with HiddenHttpMethodFilter?
That blog indicates that It is a long standing, high priority bug.
If there is no solution regarding the version 3.0.2 (3 or higher) then I have to disable Spring support forever and continue to use commons-fileupolad as suggested by the third solution on that blog omitting the PUT, DELETE and other request methods forever.
Very little changes to the code in the parseFileItems() method inside the class MultiCommonsMultipartResolver might make it upload multiple files but I couldn't succeed in my attempts (again with the Spring version 3.0.2 (3 or higher)).
For upload multiple files in one request I used this code:
I have such jsp:
<p>Select files to upload. Press Add button to add more file inputs.</p>
<table>
<tr>
<td><input name="files" type="file" multiple="true"/></td>
</tr>
<tr>
<td><input name="files" type="file" multiple="true"/></td>
</tr>
</table>
<br/><input type="submit" value="Upload" />
File upload class bean:
import org.springframework.web.multipart.commons.CommonsMultipartFile;
public class FileUploadForm {
private CommonsMultipartFile [] files;
public CommonsMultipartFile[] getFiles() {
return files;
}
public void setFiles( CommonsMultipartFile[] files ) {
this.files = files;
}
}
Controller:
#Controller
#RequestMapping("/upload")
public class FileUploadController {
#RequestMapping(method = RequestMethod.GET)
public String displayForm(ModelMap modelMap) {
modelMap.addAttribute( new FileUploadForm() );
return "uploadForm.jsp";
}
#RequestMapping(method = RequestMethod.POST)
public String save(FileUploadForm uploadForm) {
CommonsMultipartFile[] files = uploadForm.getFiles();
if(files != null && files.length != 0) {
for(MultipartFile file : files) {
System.out.println( file.getOriginalFilename() );
}
}
return "success.jsp";
}
}
This code allows to upload multiple files in one request,
and be possible to get instance of CommonsMultipartFile for each file.
The issue as mentioned in the question was fixed as of Spring 3.0.4. As such, if you happened to use that version or higher (yes, it is 4.x.x now), you would not need to read this question/answer(s) anymore.