/*
* Copyright 2002-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.web.servlet.mvc.method.annotation;
import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import javax.servlet.http.Part;
import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.junit.Before;
import org.junit.Test;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.MethodParameter;
import org.springframework.core.annotation.SynthesizingMethodParameter;
import org.springframework.http.HttpInputMessage;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.mock.web.test.MockHttpServletRequest;
import org.springframework.mock.web.test.MockHttpServletResponse;
import org.springframework.mock.web.test.MockMultipartFile;
import org.springframework.mock.web.test.MockMultipartHttpServletRequest;
import org.springframework.mock.web.test.MockPart;
import org.springframework.util.ReflectionUtils;
import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.WebDataBinder;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.support.WebDataBinderFactory;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.ServletWebRequest;
import org.springframework.web.method.support.ModelAndViewContainer;
import org.springframework.web.multipart.MultipartException;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.support.MissingServletRequestPartException;
import static org.junit.Assert.*;
import static org.mockito.BDDMockito.*;
/**
* Test fixture with {@link RequestPartMethodArgumentResolver} and mock {@link HttpMessageConverter}.
*
* @author Rossen Stoyanchev
* @author Brian Clozel
*/
public class RequestPartMethodArgumentResolverTests {
private HttpMessageConverter<SimpleBean> messageConverter;
private RequestPartMethodArgumentResolver resolver;
private MultipartFile multipartFile1;
private MultipartFile multipartFile2;
private MockMultipartHttpServletRequest multipartRequest;
private NativeWebRequest webRequest;
private MethodParameter paramRequestPart;
private MethodParameter paramNamedRequestPart;
private MethodParameter paramValidRequestPart;
private MethodParameter paramMultipartFile;
private MethodParameter paramMultipartFileList;
private MethodParameter paramMultipartFileArray;
private MethodParameter paramInt;
private MethodParameter paramMultipartFileNotAnnot;
private MethodParameter paramPart;
private MethodParameter paramPartList;
private MethodParameter paramPartArray;
private MethodParameter paramRequestParamAnnot;
private MethodParameter optionalMultipartFile;
private MethodParameter optionalMultipartFileList;
private MethodParameter optionalPart;
private MethodParameter optionalPartList;
private MethodParameter optionalRequestPart;
@Before
@SuppressWarnings("unchecked")
public void setup() throws Exception {
messageConverter = mock(HttpMessageConverter.class);
given(messageConverter.getSupportedMediaTypes()).willReturn(Collections.singletonList(MediaType.TEXT_PLAIN));
resolver = new RequestPartMethodArgumentResolver(Collections.<HttpMessageConverter<?>>singletonList(messageConverter));
reset(messageConverter);
byte[] content = "doesn't matter as long as not empty".getBytes(StandardCharsets.UTF_8);
multipartFile1 = new MockMultipartFile("requestPart", "", "text/plain", content);
multipartFile2 = new MockMultipartFile("requestPart", "", "text/plain", content);
multipartRequest = new MockMultipartHttpServletRequest();
multipartRequest.addFile(multipartFile1);
multipartRequest.addFile(multipartFile2);
multipartRequest.addFile(new MockMultipartFile("otherPart", "", "text/plain", content));
webRequest = new ServletWebRequest(multipartRequest, new MockHttpServletResponse());
Method method = ReflectionUtils.findMethod(getClass(), "handle", (Class<?>[]) null);
paramRequestPart = new SynthesizingMethodParameter(method, 0);
paramRequestPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
paramNamedRequestPart = new SynthesizingMethodParameter(method, 1);
paramValidRequestPart = new SynthesizingMethodParameter(method, 2);
paramMultipartFile = new SynthesizingMethodParameter(method, 3);
paramMultipartFileList = new SynthesizingMethodParameter(method, 4);
paramMultipartFileArray = new SynthesizingMethodParameter(method, 5);
paramInt = new SynthesizingMethodParameter(method, 6);
paramMultipartFileNotAnnot = new SynthesizingMethodParameter(method, 7);
paramMultipartFileNotAnnot.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
paramPart = new SynthesizingMethodParameter(method, 8);
paramPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
paramPartList = new SynthesizingMethodParameter(method, 9);
paramPartArray = new SynthesizingMethodParameter(method, 10);
paramRequestParamAnnot = new SynthesizingMethodParameter(method, 11);
optionalMultipartFile = new SynthesizingMethodParameter(method, 12);
optionalMultipartFile.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
optionalMultipartFileList = new SynthesizingMethodParameter(method, 13);
optionalMultipartFileList.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
optionalPart = new SynthesizingMethodParameter(method, 14);
optionalPart.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
optionalPartList = new SynthesizingMethodParameter(method, 15);
optionalPartList.initParameterNameDiscovery(new LocalVariableTableParameterNameDiscoverer());
optionalRequestPart = new SynthesizingMethodParameter(method, 16);
}
@Test
public void supportsParameter() {
assertTrue(resolver.supportsParameter(paramRequestPart));
assertTrue(resolver.supportsParameter(paramNamedRequestPart));
assertTrue(resolver.supportsParameter(paramValidRequestPart));
assertTrue(resolver.supportsParameter(paramMultipartFile));
assertTrue(resolver.supportsParameter(paramMultipartFileList));
assertTrue(resolver.supportsParameter(paramMultipartFileArray));
assertFalse(resolver.supportsParameter(paramInt));
assertTrue(resolver.supportsParameter(paramMultipartFileNotAnnot));
assertTrue(resolver.supportsParameter(paramPart));
assertTrue(resolver.supportsParameter(paramPartList));
assertTrue(resolver.supportsParameter(paramPartArray));
assertFalse(resolver.supportsParameter(paramRequestParamAnnot));
assertTrue(resolver.supportsParameter(optionalMultipartFile));
assertTrue(resolver.supportsParameter(optionalMultipartFileList));
assertTrue(resolver.supportsParameter(optionalPart));
assertTrue(resolver.supportsParameter(optionalPartList));
assertTrue(resolver.supportsParameter(optionalRequestPart));
}
@Test
public void resolveMultipartFile() throws Exception {
Object actual = resolver.resolveArgument(paramMultipartFile, null, webRequest, null);
assertSame(multipartFile1, actual);
}
@Test
public void resolveMultipartFileList() throws Exception {
Object actual = resolver.resolveArgument(paramMultipartFileList, null, webRequest, null);
assertTrue(actual instanceof List);
assertEquals(Arrays.asList(multipartFile1, multipartFile2), actual);
}
@Test
public void resolveMultipartFileArray() throws Exception {
Object actual = resolver.resolveArgument(paramMultipartFileArray, null, webRequest, null);
assertNotNull(actual);
assertTrue(actual instanceof MultipartFile[]);
MultipartFile[] parts = (MultipartFile[]) actual;
assertEquals(2, parts.length);
assertEquals(parts[0], multipartFile1);
assertEquals(parts[1], multipartFile2);
}
@Test
public void resolveMultipartFileNotAnnotArgument() throws Exception {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
MultipartFile expected = new MockMultipartFile("multipartFileNotAnnot", "Hello World".getBytes());
request.addFile(expected);
request.addFile(new MockMultipartFile("otherPart", "", "text/plain", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramMultipartFileNotAnnot, null, webRequest, null);
assertTrue(result instanceof MultipartFile);
assertEquals("Invalid result", expected, result);
}
@Test
public void resolvePartArgument() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
MockPart expected = new MockPart("part", "Hello World".getBytes());
request.addPart(expected);
request.addPart(new MockPart("otherPart", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramPart, null, webRequest, null);
assertTrue(result instanceof Part);
assertEquals("Invalid result", expected, result);
}
@Test
public void resolvePartListArgument() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
MockPart part1 = new MockPart("requestPart", "Hello World 1".getBytes());
MockPart part2 = new MockPart("requestPart", "Hello World 2".getBytes());
request.addPart(part1);
request.addPart(part2);
request.addPart(new MockPart("otherPart", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramPartList, null, webRequest, null);
assertTrue(result instanceof List);
assertEquals(Arrays.asList(part1, part2), result);
}
@Test
public void resolvePartArrayArgument() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
MockPart part1 = new MockPart("requestPart", "Hello World 1".getBytes());
MockPart part2 = new MockPart("requestPart", "Hello World 2".getBytes());
request.addPart(part1);
request.addPart(part2);
request.addPart(new MockPart("otherPart", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object result = resolver.resolveArgument(paramPartArray, null, webRequest, null);
assertTrue(result instanceof Part[]);
Part[] parts = (Part[]) result;
assertEquals(2, parts.length);
assertEquals(parts[0], part1);
assertEquals(parts[1], part2);
}
@Test
public void resolveRequestPart() throws Exception {
testResolveArgument(new SimpleBean("foo"), paramRequestPart);
}
@Test
public void resolveNamedRequestPart() throws Exception {
testResolveArgument(new SimpleBean("foo"), paramNamedRequestPart);
}
@Test
public void resolveNamedRequestPartNotPresent() throws Exception {
testResolveArgument(null, paramNamedRequestPart);
}
@Test
public void resolveRequestPartNotValid() throws Exception {
try {
testResolveArgument(new SimpleBean(null), paramValidRequestPart);
fail("Expected exception");
}
catch (MethodArgumentNotValidException ex) {
assertEquals("requestPart", ex.getBindingResult().getObjectName());
assertEquals(1, ex.getBindingResult().getErrorCount());
assertNotNull(ex.getBindingResult().getFieldError("name"));
}
}
@Test
public void resolveRequestPartValid() throws Exception {
testResolveArgument(new SimpleBean("foo"), paramValidRequestPart);
}
@Test
public void resolveRequestPartRequired() throws Exception {
try {
testResolveArgument(null, paramValidRequestPart);
fail("Expected exception");
}
catch (MissingServletRequestPartException ex) {
assertEquals("requestPart", ex.getRequestPartName());
}
}
@Test
public void resolveRequestPartNotRequired() throws Exception {
testResolveArgument(new SimpleBean("foo"), paramValidRequestPart);
}
@Test(expected = MultipartException.class)
public void isMultipartRequest() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
resolver.resolveArgument(paramMultipartFile, new ModelAndViewContainer(), new ServletWebRequest(request), null);
}
@Test // SPR-9079
public void isMultipartRequestPut() throws Exception {
this.multipartRequest.setMethod("PUT");
Object actualValue = resolver.resolveArgument(paramMultipartFile, null, webRequest, null);
assertSame(multipartFile1, actualValue);
}
@Test
public void resolveOptionalMultipartFileArgument() throws Exception {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
MultipartFile expected = new MockMultipartFile("optionalMultipartFile", "Hello World".getBytes());
request.addFile(expected);
request.addFile(new MockMultipartFile("otherPart", "", "text/plain", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalMultipartFile, null, webRequest, null);
assertTrue(actualValue instanceof Optional);
assertEquals("Invalid result", expected, ((Optional) actualValue).get());
actualValue = resolver.resolveArgument(optionalMultipartFile, null, webRequest, null);
assertTrue(actualValue instanceof Optional);
assertEquals("Invalid result", expected, ((Optional) actualValue).get());
}
@Test
public void resolveOptionalMultipartFileArgumentNotPresent() throws Exception {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalMultipartFile, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
actualValue = resolver.resolveArgument(optionalMultipartFile, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
}
@Test
public void resolveOptionalMultipartFileArgumentWithoutMultipartRequest() throws Exception {
webRequest = new ServletWebRequest(new MockHttpServletRequest());
Object actualValue = resolver.resolveArgument(optionalMultipartFile, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
actualValue = resolver.resolveArgument(optionalMultipartFile, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
}
@Test
public void resolveOptionalMultipartFileList() throws Exception {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
MultipartFile expected = new MockMultipartFile("requestPart", "Hello World".getBytes());
request.addFile(expected);
request.addFile(new MockMultipartFile("otherPart", "", "text/plain", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
assertTrue(actualValue instanceof Optional);
assertEquals("Invalid result", Collections.singletonList(expected), ((Optional) actualValue).get());
actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
assertTrue(actualValue instanceof Optional);
assertEquals("Invalid result", Collections.singletonList(expected), ((Optional) actualValue).get());
}
@Test
public void resolveOptionalMultipartFileListNotPresent() throws Exception {
MockMultipartHttpServletRequest request = new MockMultipartHttpServletRequest();
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
}
@Test
public void resolveOptionalMultipartFileListWithoutMultipartRequest() throws Exception {
webRequest = new ServletWebRequest(new MockHttpServletRequest());
Object actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
actualValue = resolver.resolveArgument(optionalMultipartFileList, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
}
@Test
public void resolveOptionalPartArgument() throws Exception {
MockPart expected = new MockPart("optionalPart", "Hello World".getBytes());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
request.addPart(expected);
request.addPart(new MockPart("otherPart", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalPart, null, webRequest, null);
assertTrue(actualValue instanceof Optional);
assertEquals("Invalid result", expected, ((Optional) actualValue).get());
actualValue = resolver.resolveArgument(optionalPart, null, webRequest, null);
assertTrue(actualValue instanceof Optional);
assertEquals("Invalid result", expected, ((Optional) actualValue).get());
}
@Test
public void resolveOptionalPartArgumentNotPresent() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalPart, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
actualValue = resolver.resolveArgument(optionalPart, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
}
@Test
public void resolveOptionalPartArgumentWithoutMultipartRequest() throws Exception {
webRequest = new ServletWebRequest(new MockHttpServletRequest());
Object actualValue = resolver.resolveArgument(optionalPart, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
actualValue = resolver.resolveArgument(optionalPart, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
}
@Test
public void resolveOptionalPartList() throws Exception {
MockPart expected = new MockPart("requestPart", "Hello World".getBytes());
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
request.addPart(expected);
request.addPart(new MockPart("otherPart", "Hello World".getBytes()));
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
assertTrue(actualValue instanceof Optional);
assertEquals("Invalid result", Collections.singletonList(expected), ((Optional) actualValue).get());
actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
assertTrue(actualValue instanceof Optional);
assertEquals("Invalid result", Collections.singletonList(expected), ((Optional) actualValue).get());
}
@Test
public void resolveOptionalPartListNotPresent() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
}
@Test
public void resolveOptionalPartListWithoutMultipartRequest() throws Exception {
webRequest = new ServletWebRequest(new MockHttpServletRequest());
Object actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
actualValue = resolver.resolveArgument(optionalPartList, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
}
@Test
public void resolveOptionalRequestPart() throws Exception {
SimpleBean simpleBean = new SimpleBean("foo");
given(messageConverter.canRead(SimpleBean.class, MediaType.TEXT_PLAIN)).willReturn(true);
given(messageConverter.read(eq(SimpleBean.class), isA(HttpInputMessage.class))).willReturn(simpleBean);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
Object actualValue = resolver.resolveArgument(optionalRequestPart, mavContainer, webRequest, new ValidatingBinderFactory());
assertEquals("Invalid argument value", Optional.of(simpleBean), actualValue);
assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
actualValue = resolver.resolveArgument(optionalRequestPart, mavContainer, webRequest, new ValidatingBinderFactory());
assertEquals("Invalid argument value", Optional.of(simpleBean), actualValue);
assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
}
@Test
public void resolveOptionalRequestPartNotPresent() throws Exception {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod("POST");
request.setContentType("multipart/form-data");
webRequest = new ServletWebRequest(request);
Object actualValue = resolver.resolveArgument(optionalRequestPart, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
actualValue = resolver.resolveArgument(optionalRequestPart, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
}
@Test
public void resolveOptionalRequestPartWithoutMultipartRequest() throws Exception {
webRequest = new ServletWebRequest(new MockHttpServletRequest());
Object actualValue = resolver.resolveArgument(optionalRequestPart, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
actualValue = resolver.resolveArgument(optionalRequestPart, null, webRequest, null);
assertEquals("Invalid argument value", Optional.empty(), actualValue);
}
private void testResolveArgument(SimpleBean argValue, MethodParameter parameter) throws Exception {
given(messageConverter.canRead(SimpleBean.class, MediaType.TEXT_PLAIN)).willReturn(true);
given(messageConverter.read(eq(SimpleBean.class), isA(HttpInputMessage.class))).willReturn(argValue);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
Object actualValue = resolver.resolveArgument(parameter, mavContainer, webRequest, new ValidatingBinderFactory());
assertEquals("Invalid argument value", argValue, actualValue);
assertFalse("The requestHandled flag shouldn't change", mavContainer.isRequestHandled());
}
private static class SimpleBean {
@NotNull
private final String name;
public SimpleBean(String name) {
this.name = name;
}
@SuppressWarnings("unused")
public String getName() {
return name;
}
}
private final class ValidatingBinderFactory implements WebDataBinderFactory {
@Override
public WebDataBinder createBinder(NativeWebRequest webRequest, Object target, String objectName) throws Exception {
LocalValidatorFactoryBean validator = new LocalValidatorFactoryBean();
validator.afterPropertiesSet();
WebDataBinder dataBinder = new WebDataBinder(target, objectName);
dataBinder.setValidator(validator);
return dataBinder;
}
}
@SuppressWarnings("unused")
public void handle(
@RequestPart SimpleBean requestPart,
@RequestPart(value="requestPart", required=false) SimpleBean namedRequestPart,
@Valid @RequestPart("requestPart") SimpleBean validRequestPart,
@RequestPart("requestPart") MultipartFile multipartFile,
@RequestPart("requestPart") List<MultipartFile> multipartFileList,
@RequestPart("requestPart") MultipartFile[] multipartFileArray,
int i,
MultipartFile multipartFileNotAnnot,
Part part,
@RequestPart("requestPart") List<Part> partList,
@RequestPart("requestPart") Part[] partArray,
@RequestParam MultipartFile requestParamAnnot,
Optional<MultipartFile> optionalMultipartFile,
@RequestPart("requestPart") Optional<List<MultipartFile>> optionalMultipartFileList,
Optional<Part> optionalPart,
@RequestPart("requestPart") Optional<List<Part>> optionalPartList,
@RequestPart("requestPart") Optional<SimpleBean> optionalRequestPart) {
}
}