Improve request/response holder implementation (#1790)

Make use of RequestContextHolder which provides access to request / response out of the box

Signed-off-by: Marinov Avgustin <Avgustin.Marinov@bosch.com>
This commit is contained in:
Avgustin Marinov
2024-07-29 12:23:06 +03:00
committed by GitHub
parent 9cc9b23398
commit 17432925f9
9 changed files with 36 additions and 201 deletions

View File

@@ -133,9 +133,6 @@ public class DdiRootController implements DdiRootControllerRestApi {
@Autowired
private ArtifactUrlHandler artifactUrlHandler;
@Autowired
private RequestResponseContextHolder requestResponseContextHolder;
@Autowired
private EntityFactory entityFactory;
@@ -152,7 +149,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
return new ResponseEntity<>(
DataConversionHelper.createArtifacts(target, softwareModule, artifactUrlHandler, systemManagement,
new ServletServerHttpRequest(requestResponseContextHolder.getHttpServletRequest())),
new ServletServerHttpRequest(RequestResponseContextHolder.getHttpServletRequest())),
HttpStatus.OK);
}
@@ -162,7 +159,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
log.debug("getControllerBase({})", controllerId);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
.getClientIpFromRequest(RequestResponseContextHolder.getHttpServletRequest(), securityProperties));
final Action activeAction = controllerManagement.findActiveActionWithHighestWeight(controllerId).orElse(null);
final Action installedAction = controllerManagement.getInstalledActionByTarget(controllerId).orElse(null);
@@ -197,18 +194,18 @@ public class DdiRootController implements DdiRootControllerRestApi {
.loadArtifactBinary(artifact.getSha1Hash(), module.getId(), module.isEncrypted())
.orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash()));
final String ifMatch = requestResponseContextHolder.getHttpServletRequest().getHeader(HttpHeaders.IF_MATCH);
final String ifMatch = RequestResponseContextHolder.getHttpServletRequest().getHeader(HttpHeaders.IF_MATCH);
if (ifMatch != null && !HttpUtil.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) {
result = new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
} else {
final ActionStatus action = checkAndLogDownload(requestResponseContextHolder.getHttpServletRequest(),
final ActionStatus action = checkAndLogDownload(RequestResponseContextHolder.getHttpServletRequest(),
target, module.getId());
final Long statusId = action.getId();
result = FileStreamingUtil.writeFileResponse(file, artifact.getFilename(), artifact.getCreatedAt(),
requestResponseContextHolder.getHttpServletResponse(),
requestResponseContextHolder.getHttpServletRequest(),
RequestResponseContextHolder.getHttpServletResponse(),
RequestResponseContextHolder.getHttpServletRequest(),
(length, shippedSinceLastEvent,
total) -> eventPublisher.publishEvent(new DownloadProgressEvent(
tenantAware.getCurrentTenant(), statusId, shippedSinceLastEvent,
@@ -262,10 +259,10 @@ public class DdiRootController implements DdiRootControllerRestApi {
final Artifact artifact = module.getArtifactByFilename(fileName)
.orElseThrow(() -> new EntityNotFoundException(Artifact.class, fileName));
checkAndLogDownload(requestResponseContextHolder.getHttpServletRequest(), target, module.getId());
checkAndLogDownload(RequestResponseContextHolder.getHttpServletRequest(), target, module.getId());
try {
FileStreamingUtil.writeMD5FileResponse(requestResponseContextHolder.getHttpServletResponse(),
FileStreamingUtil.writeMD5FileResponse(RequestResponseContextHolder.getHttpServletResponse(),
artifact.getMd5Hash(), fileName);
} catch (final IOException e) {
log.error("Failed to stream MD5 File", e);
@@ -658,7 +655,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
private DdiDeployment generateDdiDeployment(final Target target, final Action action) {
final List<DdiChunk> chunks = DataConversionHelper.createChunks(target, action, artifactUrlHandler,
systemManagement, new ServletServerHttpRequest(requestResponseContextHolder.getHttpServletRequest()),
systemManagement, new ServletServerHttpRequest(RequestResponseContextHolder.getHttpServletRequest()),
controllerManagement);
final HandlingType downloadType = calculateDownloadType(action);
final HandlingType updateType = calculateUpdateType(action, downloadType);
@@ -719,7 +716,7 @@ public class DdiRootController implements DdiRootControllerRestApi {
public ResponseEntity<DdiConfirmationBase> getConfirmationBase(final String tenant, final String controllerId) {
log.debug("getConfirmationBase is called [controllerId={}].", controllerId);
final Target target = controllerManagement.findOrRegisterTargetIfItDoesNotExist(controllerId, IpUtil
.getClientIpFromRequest(requestResponseContextHolder.getHttpServletRequest(), securityProperties));
.getClientIpFromRequest(RequestResponseContextHolder.getHttpServletRequest(), securityProperties));
final Action activeAction = controllerManagement.findActiveActionWithHighestWeight(controllerId).orElse(null);
final DdiAutoConfirmationState autoConfirmationState = getAutoConfirmationState(controllerId);

View File

@@ -46,9 +46,6 @@ public class MgmtDownloadArtifactResource implements MgmtDownloadArtifactRestApi
@Autowired
private ArtifactManagement artifactManagement;
@Autowired
private RequestResponseContextHolder requestResponseContextHolder;
/**
* Handles the GET request for downloading an artifact.
*
@@ -74,14 +71,13 @@ public class MgmtDownloadArtifactResource implements MgmtDownloadArtifactRestApi
final DbArtifact file = artifactManagement
.loadArtifactBinary(artifact.getSha1Hash(), module.getId(), module.isEncrypted())
.orElseThrow(() -> new ArtifactBinaryNotFoundException(artifact.getSha1Hash()));
final HttpServletRequest request = requestResponseContextHolder.getHttpServletRequest();
final HttpServletRequest request = RequestResponseContextHolder.getHttpServletRequest();
final String ifMatch = request.getHeader(HttpHeaders.IF_MATCH);
if (ifMatch != null && !HttpUtil.matchesHttpHeader(ifMatch, artifact.getSha1Hash())) {
return new ResponseEntity<>(HttpStatus.PRECONDITION_FAILED);
}
return FileStreamingUtil.writeFileResponse(file, artifact.getFilename(), artifact.getCreatedAt(),
requestResponseContextHolder.getHttpServletResponse(), request, null);
RequestResponseContextHolder.getHttpServletResponse(), request, null);
}
}
}

View File

@@ -39,13 +39,9 @@ public class MgmtDownloadResource implements MgmtDownloadRestApi {
private final DownloadIdCache downloadIdCache;
private final RequestResponseContextHolder requestResponseContextHolder;
MgmtDownloadResource(final ArtifactRepository artifactRepository, final DownloadIdCache downloadIdCache,
final RequestResponseContextHolder requestResponseContextHolder) {
MgmtDownloadResource(final ArtifactRepository artifactRepository, final DownloadIdCache downloadIdCache) {
this.artifactRepository = artifactRepository;
this.downloadIdCache = downloadIdCache;
this.requestResponseContextHolder = requestResponseContextHolder;
}
@Override
@@ -77,8 +73,8 @@ public class MgmtDownloadResource implements MgmtDownloadRestApi {
}
return FileStreamingUtil.writeFileResponse(artifact, downloadId, 0L,
requestResponseContextHolder.getHttpServletResponse(),
requestResponseContextHolder.getHttpServletRequest(), null);
RequestResponseContextHolder.getHttpServletResponse(),
RequestResponseContextHolder.getHttpServletRequest(), null);
} finally {
downloadIdCache.evict(downloadId);

View File

@@ -13,14 +13,9 @@ import jakarta.servlet.http.HttpServletResponse;
import org.eclipse.hawkbit.rest.exception.ResponseExceptionHandler;
import org.eclipse.hawkbit.rest.filter.ExcludePathAwareShallowETagFilter;
import org.eclipse.hawkbit.rest.util.FilterHttpResponse;
import org.eclipse.hawkbit.rest.util.HttpResponseFactoryBean;
import org.eclipse.hawkbit.rest.util.RequestResponseContextHolder;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.hateoas.config.EnableHypermediaSupport;
import org.springframework.hateoas.config.EnableHypermediaSupport.HypermediaType;
import org.springframework.http.HttpStatus;
@@ -34,31 +29,6 @@ import org.springframework.web.context.WebApplicationContext;
@EnableHypermediaSupport(type = { HypermediaType.HAL })
public class RestConfiguration {
/**
* Create filter for {@link HttpServletResponse}.
*/
@Bean
FilterHttpResponse filterHttpResponse() {
return new FilterHttpResponse();
}
/**
* Create factory bean for {@link HttpServletResponse}.
*/
@Bean
FactoryBean<HttpServletResponse> httpResponseFactoryBean() {
return new HttpResponseFactoryBean();
}
/**
* Create factory bean for {@link HttpServletResponse}.
*/
@Bean
@Scope(value = WebApplicationContext.SCOPE_REQUEST)
RequestResponseContextHolder requestResponseContextHolder() {
return new RequestResponseContextHolder();
}
/**
* {@link ControllerAdvice} for mapping {@link RuntimeException}s from the
* repository to {@link HttpStatus} codes.

View File

@@ -1,55 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.rest.util;
import java.io.IOException;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletResponse;
/**
* Filter is needed to autowire the {@link HttpServletResponse}.
*
*/
public class FilterHttpResponse implements Filter {
private ThreadLocal<HttpServletResponse> threadLocalResponse = new ThreadLocal<>();
@Override
public void init(final FilterConfig filterConfig) throws ServletException {
// not needed
}
@Override
public void doFilter(final ServletRequest request, final ServletResponse response, final FilterChain chain)
throws IOException, ServletException {
try {
threadLocalResponse.set((HttpServletResponse) response);
chain.doFilter(request, response);
} finally {
threadLocalResponse.remove();
}
}
public HttpServletResponse getHttpServletReponse() {
return threadLocalResponse.get();
}
@Override
public void destroy() {
threadLocalResponse = null;
}
}

View File

@@ -1,56 +0,0 @@
/**
* Copyright (c) 2015 Bosch Software Innovations GmbH and others
*
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*/
package org.eclipse.hawkbit.rest.util;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.NamedBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
/**
*
* Factory bean to autowire the {@link HttpServletResponse}.
*
*/
public class HttpResponseFactoryBean implements FactoryBean<HttpServletResponse>, ApplicationContextAware, NamedBean {
public static final String FACTORY_BEAN_NAME = "httpResponseFactoryBean";
private ApplicationContext applicationContext;
@Override
public HttpServletResponse getObject() {
return applicationContext.getBean(FilterHttpResponse.class).getHttpServletReponse();
}
@Override
public Class<?> getObjectType() {
return HttpServletResponse.class;
}
@Override
public boolean isSingleton() {
return false;
}
@Override
public void setApplicationContext(final ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Override
public String getBeanName() {
return FACTORY_BEAN_NAME;
}
}

View File

@@ -9,36 +9,32 @@
*/
package org.eclipse.hawkbit.rest.util;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import java.util.Objects;
/**
* Store the request and response for the rest resources.
* Gives access to the request and response for the rest resources.
*/
public class RequestResponseContextHolder {
private HttpServletRequest httpServletRequest;
private HttpServletResponse httpServletResponse;
public HttpServletRequest getHttpServletRequest() {
return httpServletRequest;
public static HttpServletRequest getHttpServletRequest() {
return Objects
.requireNonNull(
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes(),
"Request attribute is unavailable")
.getRequest();
}
public HttpServletResponse getHttpServletResponse() {
return httpServletResponse;
public static HttpServletResponse getHttpServletResponse() {
return Objects
.requireNonNull(
(ServletRequestAttributes) RequestContextHolder.getRequestAttributes(),
"Request attribute is unavailable")
.getResponse();
}
@Autowired
public void setHttpServletRequest(final HttpServletRequest httpServletRequest) {
this.httpServletRequest = httpServletRequest;
}
@Resource(name = HttpResponseFactoryBean.FACTORY_BEAN_NAME)
public void setHttpServletResponse(final HttpServletResponse httpServletResponse) {
this.httpServletResponse = httpServletResponse;
}
}
}

View File

@@ -13,7 +13,6 @@ import org.eclipse.hawkbit.repository.jpa.RepositoryApplicationConfiguration;
import org.eclipse.hawkbit.repository.test.TestConfiguration;
import org.eclipse.hawkbit.repository.test.util.AbstractIntegrationTest;
import org.eclipse.hawkbit.rest.filter.ExcludePathAwareShallowETagFilter;
import org.eclipse.hawkbit.rest.util.FilterHttpResponse;
import org.junit.jupiter.api.BeforeEach;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
@@ -31,17 +30,14 @@ import org.springframework.web.filter.CharacterEncodingFilter;
* Abstract Test for Rest tests.
*/
@WebAppConfiguration
@ContextConfiguration(classes = { RestConfiguration.class, RepositoryApplicationConfiguration.class,
TestConfiguration.class})
@ContextConfiguration(classes = {
RestConfiguration.class, RepositoryApplicationConfiguration.class, TestConfiguration.class})
@Import(TestChannelBinderConfiguration.class)
@AutoConfigureMockMvc
public abstract class AbstractRestIntegrationTest extends AbstractIntegrationTest {
protected MockMvc mvc;
@Autowired
private FilterHttpResponse filterHttpResponse;
@Autowired
private CharacterEncodingFilter characterEncodingFilter;
@@ -62,7 +58,6 @@ public abstract class AbstractRestIntegrationTest extends AbstractIntegrationTes
new ExcludePathAwareShallowETagFilter("/rest/v1/softwaremodules/{smId}/artifacts/{artId}/download",
"/{tenant}/controller/v1/{controllerId}/softwaremodules/{softwareModuleId}/artifacts/**",
"/api/v1/downloadserver/**"));
createMvcWebAppContext.addFilter(filterHttpResponse);
return createMvcWebAppContext;
}