SPR-7117 - Add ability for CNVR to search view names that use file extensions
This commit is contained in:
parent
f8a05da1c9
commit
c1f18582e7
|
|
@ -23,6 +23,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import javax.activation.FileTypeMap;
|
||||
|
|
@ -229,7 +230,7 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
|
||||
/**
|
||||
* Indicates whether to use the Java Activation Framework to map from file extensions to media types.
|
||||
* <p>Default is {@code true}, i.e. the Java Activation Framework is used.
|
||||
* <p>Default is {@code true}, i.e. the Java Activation Framework is used (if available).
|
||||
*/
|
||||
public void setUseJaf(boolean useJaf) {
|
||||
this.useJaf = useJaf;
|
||||
|
|
@ -360,44 +361,14 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
public View resolveViewName(String viewName, Locale locale) throws Exception {
|
||||
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
|
||||
Assert.isInstanceOf(ServletRequestAttributes.class, attrs);
|
||||
ServletRequestAttributes servletAttrs = (ServletRequestAttributes) attrs;
|
||||
|
||||
List<MediaType> requestedMediaTypes = getMediaTypes(servletAttrs.getRequest());
|
||||
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
|
||||
|
||||
List<View> candidateViews = new ArrayList<View>();
|
||||
for (ViewResolver viewResolver : this.viewResolvers) {
|
||||
View view = viewResolver.resolveViewName(viewName, locale);
|
||||
if (view != null) {
|
||||
candidateViews.add(view);
|
||||
}
|
||||
}
|
||||
if (!CollectionUtils.isEmpty(this.defaultViews)) {
|
||||
candidateViews.addAll(this.defaultViews);
|
||||
}
|
||||
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
|
||||
|
||||
MediaType bestRequestedMediaType = null;
|
||||
View bestView = null;
|
||||
for (MediaType requestedMediaType : requestedMediaTypes) {
|
||||
for (View candidateView : candidateViews) {
|
||||
if (StringUtils.hasText(candidateView.getContentType())) {
|
||||
MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType());
|
||||
if (requestedMediaType.includes(candidateContentType)) {
|
||||
bestRequestedMediaType = requestedMediaType;
|
||||
bestView = candidateView;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestView != null) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
View bestView = getBestView(candidateViews, requestedMediaTypes);
|
||||
|
||||
if (bestView != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Returning [" + bestView + "] based on requested media type '" +
|
||||
bestRequestedMediaType + "'");
|
||||
}
|
||||
return bestView;
|
||||
}
|
||||
else {
|
||||
|
|
@ -416,6 +387,71 @@ public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport
|
|||
}
|
||||
}
|
||||
|
||||
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
|
||||
throws Exception {
|
||||
List<View> candidateViews = new ArrayList<View>();
|
||||
|
||||
for (ViewResolver viewResolver : this.viewResolvers) {
|
||||
View view = viewResolver.resolveViewName(viewName, locale);
|
||||
if (view != null) {
|
||||
candidateViews.add(view);
|
||||
}
|
||||
for (MediaType requestedMediaType : requestedMediaTypes) {
|
||||
List<String> extensions = getExtensionsForMediaType(requestedMediaType);
|
||||
for (String extension : extensions) {
|
||||
String viewNameWithExtension = viewName + "." + extension;
|
||||
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
|
||||
if (view != null) {
|
||||
candidateViews.add(view);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!CollectionUtils.isEmpty(this.defaultViews)) {
|
||||
candidateViews.addAll(this.defaultViews);
|
||||
}
|
||||
return candidateViews;
|
||||
}
|
||||
|
||||
private List<String> getExtensionsForMediaType(MediaType requestedMediaType) {
|
||||
List<String> result = new ArrayList<String>();
|
||||
for (Entry<String, MediaType> entry : mediaTypes.entrySet()) {
|
||||
if (requestedMediaType.includes(entry.getValue())) {
|
||||
result.add(entry.getKey());
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private View getBestView(List<View> candidateViews, List<MediaType> requestedMediaTypes) {
|
||||
MediaType bestRequestedMediaType = null;
|
||||
View bestView = null;
|
||||
for (MediaType requestedMediaType : requestedMediaTypes) {
|
||||
for (View candidateView : candidateViews) {
|
||||
if (StringUtils.hasText(candidateView.getContentType())) {
|
||||
MediaType candidateContentType = MediaType.parseMediaType(candidateView.getContentType());
|
||||
if (requestedMediaType.includes(candidateContentType)) {
|
||||
bestRequestedMediaType = requestedMediaType;
|
||||
bestView = candidateView;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestView != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(
|
||||
"Returning [" + bestView + "] based on requested media type '" + bestRequestedMediaType +
|
||||
"'");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return bestView;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Inner class to avoid hard-coded JAF dependency.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
package org.springframework.web.servlet.view;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
|
|
@ -24,6 +25,7 @@ import java.util.Locale;
|
|||
import java.util.Map;
|
||||
|
||||
import static org.easymock.EasyMock.*;
|
||||
import org.junit.After;
|
||||
import static org.junit.Assert.*;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
|
@ -43,9 +45,18 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
private ContentNegotiatingViewResolver viewResolver;
|
||||
|
||||
private MockHttpServletRequest request;
|
||||
|
||||
@Before
|
||||
public void createViewResolver() {
|
||||
viewResolver = new ContentNegotiatingViewResolver();
|
||||
request = new MockHttpServletRequest("GET", "/test");
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
}
|
||||
|
||||
@After
|
||||
public void resetRequestContextHolder() {
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -69,7 +80,7 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
@Test
|
||||
public void getMediaTypeFilename() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test.html?foo=bar");
|
||||
request.setRequestURI("/test.html?foo=bar");
|
||||
List<MediaType> result = viewResolver.getMediaTypes(request);
|
||||
assertEquals("Invalid content type", Collections.singletonList(new MediaType("text", "html")), result);
|
||||
viewResolver.setMediaTypes(Collections.singletonMap("html", "application/xhtml+xml"));
|
||||
|
|
@ -82,7 +93,6 @@ public class ContentNegotiatingViewResolverTests {
|
|||
public void getMediaTypeParameter() {
|
||||
viewResolver.setFavorParameter(true);
|
||||
viewResolver.setMediaTypes(Collections.singletonMap("html", "application/xhtml+xml"));
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
|
||||
request.addParameter("format", "html");
|
||||
List<MediaType> result = viewResolver.getMediaTypes(request);
|
||||
assertEquals("Invalid content type", Collections.singletonList(new MediaType("application", "xhtml+xml")),
|
||||
|
|
@ -91,7 +101,6 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
@Test
|
||||
public void getMediaTypeAcceptHeader() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
|
||||
request.addHeader("Accept", "text/html,application/xml;q=0.9,application/xhtml+xml,*/*;q=0.8");
|
||||
List<MediaType> result = viewResolver.getMediaTypes(request);
|
||||
assertEquals("Invalid amount of media types", 4, result.size());
|
||||
|
|
@ -105,7 +114,6 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
@Test
|
||||
public void getDefaultContentType() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
|
||||
request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
viewResolver.setIgnoreAcceptHeader(true);
|
||||
viewResolver.setDefaultContentType(new MediaType("application", "pdf"));
|
||||
|
|
@ -114,21 +122,92 @@ public class ContentNegotiatingViewResolverTests {
|
|||
assertEquals("Invalid content type", new MediaType("application", "pdf"), result.get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveViewNameWithPathExtension() throws Exception {
|
||||
request.setRequestURI("/test.xls");
|
||||
|
||||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
||||
View viewMock = createMock("application_xls", View.class);
|
||||
|
||||
String viewName = "view";
|
||||
Locale locale = Locale.ENGLISH;
|
||||
|
||||
expect(viewResolverMock.resolveViewName(viewName, locale)).andReturn(null);
|
||||
expect(viewResolverMock.resolveViewName(viewName + ".xls", locale)).andReturn(viewMock);
|
||||
expect(viewMock.getContentType()).andReturn("application/vnd.ms-excel").anyTimes();
|
||||
|
||||
replay(viewResolverMock, viewMock);
|
||||
|
||||
View result = viewResolver.resolveViewName(viewName, locale);
|
||||
assertSame("Invalid view", viewMock, result);
|
||||
|
||||
verify(viewResolverMock, viewMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveViewNameWithAcceptHeader() throws Exception {
|
||||
request.addHeader("Accept", "application/vnd.ms-excel");
|
||||
|
||||
viewResolver.setMediaTypes(Collections.singletonMap("xls", "application/vnd.ms-excel"));
|
||||
|
||||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
||||
View viewMock = createMock("application_xls", View.class);
|
||||
|
||||
String viewName = "view";
|
||||
Locale locale = Locale.ENGLISH;
|
||||
|
||||
expect(viewResolverMock.resolveViewName(viewName, locale)).andReturn(null);
|
||||
expect(viewResolverMock.resolveViewName(viewName + ".xls", locale)).andReturn(viewMock);
|
||||
expect(viewMock.getContentType()).andReturn("application/vnd.ms-excel").anyTimes();
|
||||
|
||||
replay(viewResolverMock, viewMock);
|
||||
|
||||
View result = viewResolver.resolveViewName(viewName, locale);
|
||||
assertSame("Invalid view", viewMock, result);
|
||||
verify(viewResolverMock, viewMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveViewNameWithRequestParameter() throws Exception {
|
||||
request.addParameter("format", "xls");
|
||||
|
||||
viewResolver.setFavorParameter(true);
|
||||
viewResolver.setMediaTypes(Collections.singletonMap("xls", "application/vnd.ms-excel"));
|
||||
|
||||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
||||
View viewMock = createMock("application_xls", View.class);
|
||||
|
||||
String viewName = "view";
|
||||
Locale locale = Locale.ENGLISH;
|
||||
|
||||
expect(viewResolverMock.resolveViewName(viewName, locale)).andReturn(null);
|
||||
expect(viewResolverMock.resolveViewName(viewName + ".xls", locale)).andReturn(viewMock);
|
||||
expect(viewMock.getContentType()).andReturn("application/vnd.ms-excel").anyTimes();
|
||||
|
||||
replay(viewResolverMock, viewMock);
|
||||
|
||||
View result = viewResolver.resolveViewName(viewName, locale);
|
||||
assertSame("Invalid view", viewMock, result);
|
||||
|
||||
verify(viewResolverMock, viewMock);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resolveViewNameWithDefaultContentType() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
|
||||
request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
|
||||
viewResolver.setIgnoreAcceptHeader(true);
|
||||
viewResolver.setDefaultContentType(new MediaType("application", "xml"));
|
||||
|
||||
ViewResolver viewResolverMock1 = createMock(ViewResolver.class);
|
||||
ViewResolver viewResolverMock2 = createMock(ViewResolver.class);
|
||||
List<ViewResolver> viewResolverMocks = new ArrayList<ViewResolver>();
|
||||
viewResolverMocks.add(viewResolverMock1);
|
||||
viewResolverMocks.add(viewResolverMock2);
|
||||
viewResolver.setViewResolvers(viewResolverMocks);
|
||||
ViewResolver viewResolverMock1 = createMock("viewResolver1", ViewResolver.class);
|
||||
ViewResolver viewResolverMock2 = createMock("viewResolver2", ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Arrays.asList(viewResolverMock1, viewResolverMock2));
|
||||
|
||||
View viewMock1 = createMock("application_xml", View.class);
|
||||
View viewMock2 = createMock("text_html", View.class);
|
||||
|
|
@ -151,16 +230,11 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
@Test
|
||||
public void resolveViewNameAcceptHeader() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
|
||||
request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
|
||||
ViewResolver viewResolverMock1 = createMock(ViewResolver.class);
|
||||
ViewResolver viewResolverMock2 = createMock(ViewResolver.class);
|
||||
List<ViewResolver> viewResolverMocks = new ArrayList<ViewResolver>();
|
||||
viewResolverMocks.add(viewResolverMock1);
|
||||
viewResolverMocks.add(viewResolverMock2);
|
||||
viewResolver.setViewResolvers(viewResolverMocks);
|
||||
viewResolver.setViewResolvers(Arrays.asList(viewResolverMock1, viewResolverMock2));
|
||||
|
||||
View viewMock1 = createMock("application_xml", View.class);
|
||||
View viewMock2 = createMock("text_html", View.class);
|
||||
|
|
@ -183,16 +257,11 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
@Test
|
||||
public void resolveViewNameAcceptHeaderDefaultView() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
|
||||
request.addHeader("Accept", "application/json");
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
|
||||
ViewResolver viewResolverMock1 = createMock(ViewResolver.class);
|
||||
ViewResolver viewResolverMock2 = createMock(ViewResolver.class);
|
||||
List<ViewResolver> viewResolverMocks = new ArrayList<ViewResolver>();
|
||||
viewResolverMocks.add(viewResolverMock1);
|
||||
viewResolverMocks.add(viewResolverMock2);
|
||||
viewResolver.setViewResolvers(viewResolverMocks);
|
||||
viewResolver.setViewResolvers(Arrays.asList(viewResolverMock1, viewResolverMock2));
|
||||
|
||||
View viewMock1 = createMock("application_xml", View.class);
|
||||
View viewMock2 = createMock("text_html", View.class);
|
||||
|
|
@ -221,15 +290,11 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
@Test
|
||||
public void resolveViewNameFilename() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test.html");
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
request.setRequestURI("/test.html");
|
||||
|
||||
ViewResolver viewResolverMock1 = createMock(ViewResolver.class);
|
||||
ViewResolver viewResolverMock2 = createMock(ViewResolver.class);
|
||||
List<ViewResolver> viewResolverMocks = new ArrayList<ViewResolver>();
|
||||
viewResolverMocks.add(viewResolverMock1);
|
||||
viewResolverMocks.add(viewResolverMock2);
|
||||
viewResolver.setViewResolvers(viewResolverMocks);
|
||||
ViewResolver viewResolverMock1 = createMock("viewResolver1", ViewResolver.class);
|
||||
ViewResolver viewResolverMock2 = createMock("viewResolver2", ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Arrays.asList(viewResolverMock1, viewResolverMock2));
|
||||
|
||||
View viewMock1 = createMock("application_xml", View.class);
|
||||
View viewMock2 = createMock("text_html", View.class);
|
||||
|
|
@ -238,7 +303,9 @@ public class ContentNegotiatingViewResolverTests {
|
|||
Locale locale = Locale.ENGLISH;
|
||||
|
||||
expect(viewResolverMock1.resolveViewName(viewName, locale)).andReturn(viewMock1);
|
||||
expect(viewResolverMock2.resolveViewName(viewName, locale)).andReturn(viewMock2);
|
||||
expect(viewResolverMock1.resolveViewName(viewName + ".html", locale)).andReturn(null);
|
||||
expect(viewResolverMock2.resolveViewName(viewName, locale)).andReturn(null);
|
||||
expect(viewResolverMock2.resolveViewName(viewName + ".html", locale)).andReturn(viewMock2);
|
||||
expect(viewMock1.getContentType()).andReturn("application/xml").anyTimes();
|
||||
expect(viewMock2.getContentType()).andReturn("text/html;charset=ISO-8859-1").anyTimes();
|
||||
|
||||
|
|
@ -252,8 +319,7 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
@Test
|
||||
public void resolveViewNameFilenameDefaultView() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test.json");
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
request.setRequestURI("/test.json");
|
||||
|
||||
Map<String, String> mediaTypes = new HashMap<String, String>();
|
||||
mediaTypes.put("json", "application/json");
|
||||
|
|
@ -261,10 +327,7 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
ViewResolver viewResolverMock1 = createMock(ViewResolver.class);
|
||||
ViewResolver viewResolverMock2 = createMock(ViewResolver.class);
|
||||
List<ViewResolver> viewResolverMocks = new ArrayList<ViewResolver>();
|
||||
viewResolverMocks.add(viewResolverMock1);
|
||||
viewResolverMocks.add(viewResolverMock2);
|
||||
viewResolver.setViewResolvers(viewResolverMocks);
|
||||
viewResolver.setViewResolvers(Arrays.asList(viewResolverMock1, viewResolverMock2));
|
||||
|
||||
View viewMock1 = createMock("application_xml", View.class);
|
||||
View viewMock2 = createMock("text_html", View.class);
|
||||
|
|
@ -278,7 +341,9 @@ public class ContentNegotiatingViewResolverTests {
|
|||
Locale locale = Locale.ENGLISH;
|
||||
|
||||
expect(viewResolverMock1.resolveViewName(viewName, locale)).andReturn(viewMock1);
|
||||
expect(viewResolverMock1.resolveViewName(viewName + ".json", locale)).andReturn(null);
|
||||
expect(viewResolverMock2.resolveViewName(viewName, locale)).andReturn(viewMock2);
|
||||
expect(viewResolverMock2.resolveViewName(viewName + ".json", locale)).andReturn(null);
|
||||
expect(viewMock1.getContentType()).andReturn("application/xml").anyTimes();
|
||||
expect(viewMock2.getContentType()).andReturn("text/html;charset=ISO-8859-1").anyTimes();
|
||||
expect(viewMock3.getContentType()).andReturn("application/json").anyTimes();
|
||||
|
|
@ -293,9 +358,7 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
@Test
|
||||
public void resolveViewContentTypeNull() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
|
||||
request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
|
||||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
|
@ -318,9 +381,7 @@ public class ContentNegotiatingViewResolverTests {
|
|||
|
||||
@Test
|
||||
public void resolveViewNoMatch() throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
|
||||
request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9");
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
|
||||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
|
@ -344,9 +405,7 @@ public class ContentNegotiatingViewResolverTests {
|
|||
@Test
|
||||
public void resolveViewNoMatchUseUnacceptableStatus() throws Exception {
|
||||
viewResolver.setUseNotAcceptableStatusCode(true);
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/test");
|
||||
request.addHeader("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9");
|
||||
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));
|
||||
|
||||
ViewResolver viewResolverMock = createMock(ViewResolver.class);
|
||||
viewResolver.setViewResolvers(Collections.singletonList(viewResolverMock));
|
||||
|
|
|
|||
Loading…
Reference in New Issue