diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/ClientHttpRequestExecution.java b/org.springframework.web/src/main/java/org/springframework/http/client/ClientHttpRequestExecution.java new file mode 100644 index 00000000000..df6e10c6f0e --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/client/ClientHttpRequestExecution.java @@ -0,0 +1,44 @@ +/* + * Copyright 2002-2011 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.http.client; + +import java.io.IOException; + +import org.springframework.http.HttpRequest; + +/** + * Represents the context of a client-side HTTP request execution. + * + *

Used to invoke the next interceptor in the interceptor chain, or - if the calling interceptor is last - execute + * the request itself. + * + * @author Arjen Poutsma + * @see ClientHttpRequestInterceptor + * @since 3.1 + */ +public interface ClientHttpRequestExecution { + + /** + * Execute the request with the given request attributes and body, and return the response. + * + * @param request the request, containing method, URI, and headers + * @param body the body of the request to execute + * @return the response + * @throws IOException in case of I/O errors + */ + ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException; +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/ClientHttpRequestInterceptor.java b/org.springframework.web/src/main/java/org/springframework/http/client/ClientHttpRequestInterceptor.java new file mode 100644 index 00000000000..f6b9f1a8366 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/client/ClientHttpRequestInterceptor.java @@ -0,0 +1,63 @@ +/* + * Copyright 2002-2011 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.http.client; + +import java.io.IOException; + +import org.springframework.http.HttpRequest; + +/** + * Intercepts client-side HTTP requests. Implementations of this interface can be {@linkplain + * org.springframework.web.client.RestTemplate#setInterceptors(ClientHttpRequestInterceptor[]) registered} with the + * {@link org.springframework.web.client.RestTemplate RestTemplate}, as to modify the outgoing {@link ClientHttpRequest} + * and/or the incoming {@link ClientHttpResponse}. + * + *

The main entry point for interceptors is {@link #intercept(HttpRequest, byte[], ClientHttpRequestExecution)}. + * + * @author Arjen Poutsma + * @since 3.1 + */ +public interface ClientHttpRequestInterceptor { + + /** + * Intercept the given request, and return a response. The given {@link ClientHttpRequestExecution} allows + * the interceptor to pass on the request and response to the next entity in the chain. + * + *

A typical implementation of this method would follow the following pattern: + *

    + *
  1. Examine the {@linkplain HttpRequest request} and body
  2. + *
  3. Optionally {@linkplain org.springframework.http.client.support.HttpRequestWrapper wrap} the request to filter HTTP attributes.
  4. + *
  5. Optionally modify the body of the request.
  6. + *
  7. Either + * + *
  8. Optionally wrap the response to filter HTTP attributes.
  9. + *
+ * + * @param request the request, containing method, URI, and headers + * @param body the body of the request + * @param execution the request execution + * @return the response + * @throws IOException in case of I/O errors + */ + ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException; + +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java b/org.springframework.web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java new file mode 100644 index 00000000000..598cfa033ff --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequest.java @@ -0,0 +1,96 @@ +/* + * Copyright 2002-2011 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.http.client; + +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; +import java.util.Iterator; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.util.FileCopyUtils; + +/** + * Wrapper for a {@link ClientHttpRequest} that has support for {@link ClientHttpRequestInterceptor}s. + * + * @author Arjen Poutsma + * @since 3.1 + */ +class InterceptingClientHttpRequest extends AbstractBufferingClientHttpRequest { + + private final ClientHttpRequestFactory requestFactory; + + private final ClientHttpRequestInterceptor[] interceptors; + + private HttpMethod method; + + private URI uri; + + protected InterceptingClientHttpRequest(ClientHttpRequestFactory requestFactory, + ClientHttpRequestInterceptor[] interceptors, + URI uri, + HttpMethod method) { + this.requestFactory = requestFactory; + this.interceptors = interceptors; + this.method = method; + this.uri = uri; + } + + public HttpMethod getMethod() { + return method; + } + + public URI getURI() { + return uri; + } + + @Override + protected final ClientHttpResponse executeInternal(HttpHeaders headers, byte[] bufferedOutput) throws IOException { + RequestExecution requestExecution = new RequestExecution(); + + return requestExecution.execute(this, bufferedOutput); + } + + private class RequestExecution implements ClientHttpRequestExecution { + + private final Iterator iterator; + + private RequestExecution() { + this.iterator = Arrays.asList(interceptors).iterator(); + } + + public ClientHttpResponse execute(HttpRequest request, byte[] body) throws IOException { + if (iterator.hasNext()) { + ClientHttpRequestInterceptor nextInterceptor = iterator.next(); + return nextInterceptor.intercept(request, body, this); + } + else { + ClientHttpRequest delegate = requestFactory.createRequest(request.getURI(), request.getMethod()); + + delegate.getHeaders().putAll(request.getHeaders()); + + if (body.length > 0) { + FileCopyUtils.copy(body, delegate.getBody()); + } + return delegate.execute(); + } + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequestFactory.java b/org.springframework.web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequestFactory.java new file mode 100644 index 00000000000..b5b0fb91e28 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/client/InterceptingClientHttpRequestFactory.java @@ -0,0 +1,53 @@ +/* + * Copyright 2002-2011 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.http.client; + +import java.io.IOException; +import java.net.URI; + +import org.springframework.http.HttpMethod; +import org.springframework.util.Assert; + +/** + * Wrapper for a {@link ClientHttpRequestFactory} that has support for {@link ClientHttpRequestInterceptor}s. + * + * @author Arjen Poutsma + * @since 3.1 + */ +public class InterceptingClientHttpRequestFactory implements ClientHttpRequestFactory { + + private final ClientHttpRequestFactory requestFactory; + + private final ClientHttpRequestInterceptor[] interceptors; + + /** + * Creates a new instance of the {@code InterceptingClientHttpRequestFactory} with the given parameters. + * + * @param requestFactory the request factory to wrap + * @param interceptors the interceptors that are to be applied. Can be {@code null}. + */ + public InterceptingClientHttpRequestFactory(ClientHttpRequestFactory requestFactory, + ClientHttpRequestInterceptor[] interceptors) { + Assert.notNull(requestFactory, "'requestFactory' must not be null"); + this.requestFactory = requestFactory; + this.interceptors = interceptors != null ? interceptors : new ClientHttpRequestInterceptor[0]; + } + + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { + return new InterceptingClientHttpRequest(requestFactory, interceptors, uri, httpMethod); + } +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java b/org.springframework.web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java new file mode 100644 index 00000000000..969feefda50 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/client/support/HttpRequestWrapper.java @@ -0,0 +1,76 @@ +/* + * Copyright 2002-2011 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.http.client.support; + +import java.net.URI; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.util.Assert; + +/** + * Provides a convenient implementation of the {@link HttpRequest} interface that can be overridden to adapt the + * request. Methods default to calling through to the wrapped request object. + * + * @author Arjen Poutsma + * @since 3.1 + */ +public class HttpRequestWrapper implements HttpRequest { + + private final HttpRequest request; + + + /** + * Creates a new {@code HttpRequest} wrapping the given request object. + * + * @param request the request object to be wrapped + */ + public HttpRequestWrapper(HttpRequest request) { + Assert.notNull(request, "'request' must not be null"); + this.request = request; + } + + /** + * Returns the wrapped request. + */ + public HttpRequest getRequest() { + return request; + } + + /** + * Returns the method of the wrapped request. + */ + public HttpMethod getMethod() { + return this.request.getMethod(); + } + + /** + * Returns the URI of the wrapped request. + */ + public URI getURI() { + return this.request.getURI(); + } + + /** + * Returns the headers of the wrapped request. + */ + public HttpHeaders getHeaders() { + return this.request.getHeaders(); + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java b/org.springframework.web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java new file mode 100644 index 00000000000..e5cfb51e164 --- /dev/null +++ b/org.springframework.web/src/main/java/org/springframework/http/client/support/InterceptingHttpAccessor.java @@ -0,0 +1,61 @@ +/* + * Copyright 2002-2011 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.http.client.support; + +import org.springframework.http.client.ClientHttpRequestFactory; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.InterceptingClientHttpRequestFactory; +import org.springframework.util.ObjectUtils; + +/** + * Base class for {@link org.springframework.web.client.RestTemplate} and other HTTP accessing gateway helpers, adding + * interceptor-related properties to {@link HttpAccessor}'s common properties. + * + *

Not intended to be used directly. See {@link org.springframework.web.client.RestTemplate}. + * + * @author Arjen Poutsma + */ +public abstract class InterceptingHttpAccessor extends HttpAccessor { + + private ClientHttpRequestInterceptor[] interceptors; + + /** + * Sets the request interceptors that this accessor should use. + */ + public void setInterceptors(ClientHttpRequestInterceptor[] interceptors) { + this.interceptors = interceptors; + } + + /** + * Return the request interceptor that this accessor uses. + */ + public ClientHttpRequestInterceptor[] getInterceptors() { + return interceptors; + } + + @Override + public ClientHttpRequestFactory getRequestFactory() { + ClientHttpRequestFactory delegate = super.getRequestFactory(); + if (!ObjectUtils.isEmpty(getInterceptors())) { + return new InterceptingClientHttpRequestFactory(delegate, getInterceptors()); + } + else { + return delegate; + } + } + +} diff --git a/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java b/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java index 79094d7f4c2..f6c3c957145 100644 --- a/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java +++ b/org.springframework.web/src/main/java/org/springframework/web/client/RestTemplate.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2010 the original author or authors. + * Copyright 2002-2011 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. @@ -33,7 +33,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.http.client.ClientHttpRequest; import org.springframework.http.client.ClientHttpRequestFactory; import org.springframework.http.client.ClientHttpResponse; -import org.springframework.http.client.support.HttpAccessor; +import org.springframework.http.client.support.InterceptingHttpAccessor; import org.springframework.http.converter.ByteArrayHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.ResourceHttpMessageConverter; @@ -113,7 +113,7 @@ import org.springframework.web.util.UriUtils; * @see ResponseErrorHandler * @since 3.0 */ -public class RestTemplate extends HttpAccessor implements RestOperations { +public class RestTemplate extends InterceptingHttpAccessor implements RestOperations { private static final boolean jaxb2Present = ClassUtils.isPresent("javax.xml.bind.Binder", RestTemplate.class.getClassLoader()); diff --git a/org.springframework.web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTest.java b/org.springframework.web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTest.java new file mode 100644 index 00000000000..a292ced00e1 --- /dev/null +++ b/org.springframework.web/src/test/java/org/springframework/http/client/InterceptingClientHttpRequestFactoryTest.java @@ -0,0 +1,308 @@ +/* + * Copyright 2002-2011 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.http.client; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.URI; +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.client.support.HttpRequestWrapper; + +import static org.junit.Assert.*; + +/** + * @author Arjen Poutsma + */ +public class InterceptingClientHttpRequestFactoryTest { + + private InterceptingClientHttpRequestFactory requestFactory; + + private RequestFactoryMock requestFactoryMock; + + private RequestMock requestMock; + + private ResponseMock responseMock; + + @Before + public void setUp() throws Exception { + + requestFactoryMock = new RequestFactoryMock(); + requestMock = new RequestMock(); + responseMock = new ResponseMock(); + + } + + @Test + public void basic() throws Exception { + NoOpInterceptor interceptor1 = new NoOpInterceptor(); + NoOpInterceptor interceptor2 = new NoOpInterceptor(); + NoOpInterceptor interceptor3 = new NoOpInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new ClientHttpRequestInterceptor[]{interceptor1, interceptor2, interceptor3}); + + ClientHttpRequest request = requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET); + ClientHttpResponse response = request.execute(); + + assertTrue(interceptor1.invoked); + assertTrue(interceptor2.invoked); + assertTrue(interceptor3.invoked); + assertTrue(requestMock.executed); + assertSame(responseMock, response); + } + + @Test + public void noExecution() throws Exception { + ClientHttpRequestInterceptor interceptor1 = new ClientHttpRequestInterceptor() { + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + return responseMock; + } + }; + + NoOpInterceptor interceptor2 = new NoOpInterceptor(); + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new ClientHttpRequestInterceptor[]{interceptor1, interceptor2}); + + ClientHttpRequest request = requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET); + ClientHttpResponse response = request.execute(); + + assertFalse(interceptor2.invoked); + assertFalse(requestMock.executed); + assertSame(responseMock, response); + } + + @Test + public void changeHeaders() throws Exception { + final String headerName = "Foo"; + final String headerValue = "Bar"; + ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() { + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + + return execution.execute(new HttpRequestWrapper(request) { + @Override + public HttpHeaders getHeaders() { + HttpHeaders headers = new HttpHeaders(); + headers.set(headerName, headerValue); + return headers; + } + }, body); + } + }; + + requestMock = new RequestMock() { + @Override + public ClientHttpResponse execute() throws IOException { + assertEquals(headerValue, getHeaders().getFirst(headerName)); + return super.execute(); + } + }; + + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new ClientHttpRequestInterceptor[]{interceptor}); + + ClientHttpRequest request = requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET); + request.execute(); + } + + @Test + public void changeURI() throws Exception { + final URI changedUri = new URI("http://example.com/2"); + ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() { + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + + return execution.execute(new HttpRequestWrapper(request) { + @Override + public URI getURI() { + return changedUri; + } + + }, body); + } + }; + + requestFactoryMock = new RequestFactoryMock() { + @Override + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { + assertEquals(changedUri, uri); + return super.createRequest(uri, httpMethod); + } + }; + + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new ClientHttpRequestInterceptor[]{interceptor}); + + ClientHttpRequest request = requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET); + request.execute(); + } + + @Test + public void changeMethod() throws Exception { + final HttpMethod changedMethod = HttpMethod.POST; + ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() { + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + + return execution.execute(new HttpRequestWrapper(request) { + @Override + public HttpMethod getMethod() { + return changedMethod; + } + + }, body); + } + }; + + requestFactoryMock = new RequestFactoryMock() { + @Override + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { + assertEquals(changedMethod, httpMethod); + return super.createRequest(uri, httpMethod); + } + }; + + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new ClientHttpRequestInterceptor[]{interceptor}); + + ClientHttpRequest request = requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET); + request.execute(); + } + + @Test + public void changeBody() throws Exception { + final byte[] changedBody = "Foo".getBytes(); + ClientHttpRequestInterceptor interceptor = new ClientHttpRequestInterceptor() { + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + + return execution.execute(request, changedBody); + } + }; + + requestFactory = new InterceptingClientHttpRequestFactory(requestFactoryMock, + new ClientHttpRequestInterceptor[]{interceptor}); + + ClientHttpRequest request = requestFactory.createRequest(new URI("http://example.com"), HttpMethod.GET); + request.execute(); + assertTrue(Arrays.equals(changedBody, requestMock.body.toByteArray())); + } + + private static class NoOpInterceptor implements ClientHttpRequestInterceptor { + + private boolean invoked = false; + + public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) + throws IOException { + invoked = true; + return execution.execute(request, body); + } + } + + private class RequestFactoryMock implements ClientHttpRequestFactory { + + public ClientHttpRequest createRequest(URI uri, HttpMethod httpMethod) throws IOException { + requestMock.setURI(uri); + requestMock.setMethod(httpMethod); + return requestMock; + } + + } + + private class RequestMock implements ClientHttpRequest { + + private URI uri; + + private HttpMethod method; + + private HttpHeaders headers = new HttpHeaders(); + + private ByteArrayOutputStream body = new ByteArrayOutputStream(); + + private boolean executed = false; + + private RequestMock() { + } + + public URI getURI() { + return uri; + } + + public void setURI(URI uri) { + this.uri = uri; + } + + public HttpMethod getMethod() { + return method; + } + + public void setMethod(HttpMethod method) { + this.method = method; + } + + public HttpHeaders getHeaders() { + return headers; + } + + public OutputStream getBody() throws IOException { + return body; + } + + public ClientHttpResponse execute() throws IOException { + executed = true; + return responseMock; + } + } + + private static class ResponseMock implements ClientHttpResponse { + + private HttpStatus statusCode = HttpStatus.OK; + + private String statusText = ""; + + private HttpHeaders headers = new HttpHeaders(); + + public HttpStatus getStatusCode() throws IOException { + return statusCode; + } + + public String getStatusText() throws IOException { + return statusText; + } + + public HttpHeaders getHeaders() { + return headers; + } + + public InputStream getBody() throws IOException { + return null; //To change body of implemented methods use File | Settings | File Templates. + } + + public void close() { + } + } +}