Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 2912: Support folder browsing for static resources #2917

Merged
Prev Previous commit
Next Next commit
Issue #2912 Support folder browsing in share srv
  • Loading branch information
mzueva committed Nov 9, 2022
commit b18faa9dff8fa8afdca8ef3235fd9e3ca9f24a68
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import com.epam.pipeline.entity.datastorage.DataStorageException;
import com.epam.pipeline.entity.datastorage.DataStorageFile;
import com.epam.pipeline.entity.datastorage.DataStorageItemContent;
import com.epam.pipeline.entity.datastorage.DataStorageItemType;
import com.epam.pipeline.entity.datastorage.DataStorageListing;
import com.epam.pipeline.entity.datastorage.DataStorageStreamingContent;
import com.epam.pipeline.entity.datastorage.DataStorageWithShareMount;
Expand Down Expand Up @@ -369,4 +370,14 @@ public void callOffDataStorageDavMount(final Long id) {
public void updateStorageUsage(final String id) {
eventsService.ifPresent(s -> s.addReindexEvent(id));
}

@PreAuthorize(AclExpressions.STORAGE_ID_READ)
public DataStorageItemType getItemType(final Long id, final String path, final String version) {
return dataStorageManager.getItemType(id, path, version);
}

@PreAuthorize(AclExpressions.STORAGE_ID_OWNER)
public DataStorageItemType getItemTypeOwner(final Long id, final String path, final String version) {
return dataStorageManager.getItemType(id, path, version);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,26 @@ public Result<DataStorageDownloadFileUrl> generateDataStorageItemUrl(
}
}

@RequestMapping(value = "/datastorage/{id}/type", method = RequestMethod.GET)
@ResponseBody
@ApiOperation(
value = "Returns data storage item's type.",
notes = "Returns data storage item's type",
produces = MediaType.APPLICATION_JSON_VALUE)
@ApiResponses(
value = {@ApiResponse(code = HTTP_STATUS_OK, message = API_STATUS_DESCRIPTION)
})
public Result<DataStorageItemType> getStorageItemType(
@PathVariable(value = ID) final Long id,
@RequestParam(value = PATH) final String path,
@RequestParam(value = VERSION, required = false) final String version) {
if (StringUtils.hasText(version)) {
return Result.success(dataStorageApiService.getItemTypeOwner(id, path, version));
} else {
return Result.success(dataStorageApiService.getItemType(id, path, version));
}
}

@GetMapping(value = "/datastorage/{id}/generateUploadUrl")
@ResponseBody
@ApiOperation(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1183,6 +1183,12 @@ public void checkDataStorageObjectExists(final AbstractDataStorage dataStorage,
path, dataStorage.getRoot())));
}

public DataStorageItemType getItemType(final Long id,
final String path,
final String version) {
return storageProviderManager.getItemType(load(id), path, version);
}

public DataStorageItemType getItemType(final AbstractDataStorage dataStorage,
final String path,
final String version) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.epam.pipeline.rest.Result;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Response;

import java.io.InputStream;

Expand All @@ -15,4 +16,6 @@ public interface CloudPipelineApiExecutor {
byte[] getByteResponse(Call<byte[]> call);

InputStream getResponseStream(Call<ResponseBody> call);

Response<ResponseBody> getResponse(Call<ResponseBody> call);
}
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,17 @@ public InputStream getResponseStream(final Call<ResponseBody> call) {
}
}

@Override
public Response<ResponseBody> getResponse(final Call<ResponseBody> call) {
try {
final Response<ResponseBody> response = call.execute();
validateResponseStatus(response);
return response;
} catch (IOException e) {
throw new PipelineResponseIOException(e);
}
}

private byte[] internalGetByteResponse(final Call<byte[]> call) throws IOException {
try {
final Response<byte[]> response = call.execute();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,27 +17,27 @@
package com.epam.pipeline.external.datastorage.controller.resource;

import com.epam.pipeline.external.datastorage.controller.AbstractRestController;
import com.epam.pipeline.external.datastorage.exception.InvalidPathException;
import com.epam.pipeline.external.datastorage.manager.preference.PreferenceService;
import com.epam.pipeline.external.datastorage.manager.resource.StaticResourcesService;
import com.fasterxml.jackson.core.type.TypeReference;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import okhttp3.Headers;
import okhttp3.ResponseBody;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import retrofit2.Response;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

@RestController
Expand All @@ -46,23 +46,35 @@
@RequiredArgsConstructor
public class StaticResourcesController extends AbstractRestController {

private static final FileNameMap FILE_NAME_MAP = URLConnection.getFileNameMap();
private static final String STATIC_RESOURCES = "/static-resources/";
private static final String UI_STORAGE_STATIC_PREVIEW_MASK = "ui.storage.static.preview.mask";
private static final String DATA_SHARING_STATIC_RESOURCE_HEADERS = "data.sharing.static.resource.headers";
private static final String CONTENT_DISPOSITION = "Content-Disposition";
private static final String CONTENT_TYPE = "Content-Type";

private final StaticResourcesService resourcesService;
private final PreferenceService preferenceService;

@GetMapping(value = "/**")
public void getStaticFile(final HttpServletRequest request, final HttpServletResponse response)
throws IOException {
final String path = request.getPathInfo().replaceFirst(STATIC_RESOURCES, "");
final String fileName = FilenameUtils.getName(path);
final MediaType mediaType = getMediaType(fileName);
final InputStream content = resourcesService.getContent(path);
writeStreamToResponse(response, content, fileName, mediaType,
!MediaType.APPLICATION_OCTET_STREAM.equals(mediaType), getCustomHeaders(fileName));
try {
final String path = request.getPathInfo().replaceFirst(STATIC_RESOURCES, "");
final Response<ResponseBody> content = resourcesService.getContent(path);
final String fileName = FilenameUtils.getName(path);

final Headers responseHeaders = content.headers();
final String contentDisposition = responseHeaders.get(CONTENT_DISPOSITION);
final MediaType mediaType = MediaType.parseMediaType(responseHeaders.get(CONTENT_TYPE));
final Map<String, String> headers = getCustomHeaders(fileName);

headers.put(CONTENT_DISPOSITION, contentDisposition);
headers.put(CONTENT_TYPE, mediaType.getType());
writeStreamToResponse(response, content.body().byteStream(), fileName, mediaType,
!MediaType.APPLICATION_OCTET_STREAM.equals(mediaType), getCustomHeaders(fileName));
} catch (InvalidPathException e) {
response.setHeader("Location", request.getRequestURI() + "/");
response.setStatus(HttpStatus.FOUND.value());
}
}

private Map<String, String> getCustomHeaders(final String fileName) {
Expand All @@ -71,25 +83,8 @@ private Map<String, String> getCustomHeaders(final String fileName) {
new TypeReference<Map<String, Map<String, String>>>() {});
final String extension = FilenameUtils.getExtension(fileName);
if (MapUtils.isEmpty(headers) || !headers.containsKey(extension)) {
return Collections.emptyMap();
return new HashMap<>();
}
return headers.get(extension);
}

private MediaType getMediaType(final String fileName) {
final String preference = preferenceService.getValue(UI_STORAGE_STATIC_PREVIEW_MASK);
if (StringUtils.isBlank(preference)) {
return MediaType.APPLICATION_OCTET_STREAM;
}
final String[] supportedExtensions =preference.split(",");
final String extension = FilenameUtils.getExtension(fileName);
return Arrays.stream(supportedExtensions)
.filter(ext -> ext.trim().equalsIgnoreCase(extension))
.findFirst()
.map(ext -> {
final String mimeType = FILE_NAME_MAP.getContentTypeFor(fileName);
return MediaType.parseMediaType(mimeType);
})
.orElse(MediaType.APPLICATION_OCTET_STREAM);
return new HashMap<>(headers.get(extension));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import com.epam.pipeline.entity.datastorage.DataStorageDownloadFileUrl;
import com.epam.pipeline.entity.datastorage.DataStorageFile;
import com.epam.pipeline.entity.datastorage.DataStorageItemContent;
import com.epam.pipeline.entity.datastorage.DataStorageItemType;
import com.epam.pipeline.entity.datastorage.DataStorageListing;
import com.epam.pipeline.entity.datastorage.TemporaryCredentials;
import com.epam.pipeline.rest.Result;
Expand All @@ -50,6 +51,9 @@ public interface PipelineDataStorageClient {
String SHOW_VERSION = "showVersion";
String AUTHORIZATION = "Authorization";

@GET("restapi/datastorage/findByPath")
Call<Result<AbstractDataStorage>> getStorage(@Query(ID) String id, @Header(AUTHORIZATION) String token);

@GET("restapi/datastorage/{id}/load")
Call<Result<AbstractDataStorage>> getStorage(@Path(ID) long id, @Header(AUTHORIZATION) String token);

Expand Down Expand Up @@ -140,4 +144,9 @@ Call<ResponseBody> downloadFile(@Path(ID) long id,
@Query(PATH) String path,
@Query(VERSION) String version,
@Header(AUTHORIZATION) String token);

@GET("restapi/datastorage/{id}/type")
Call<Result<DataStorageItemType>> getItemType(@Path(ID) Long storageId,
@Query(PATH) String path,
@Header(AUTHORIZATION) String token);
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,39 +17,61 @@
package com.epam.pipeline.external.datastorage.manager.resource;

import com.epam.pipeline.client.pipeline.CloudPipelineApiExecutor;
import com.epam.pipeline.entity.datastorage.AbstractDataStorage;
import com.epam.pipeline.entity.datastorage.DataStorageItemType;
import com.epam.pipeline.exception.ObjectNotFoundException;
import com.epam.pipeline.external.datastorage.exception.InvalidPathException;
import com.epam.pipeline.external.datastorage.exception.ResourceNotFoundException;
import com.epam.pipeline.external.datastorage.manager.CloudPipelineApiBuilder;
import com.epam.pipeline.external.datastorage.manager.auth.PipelineAuthManager;
import com.epam.pipeline.external.datastorage.manager.datastorage.PipelineDataStorageClient;
import lombok.SneakyThrows;
import okhttp3.ResponseBody;
import org.springframework.stereotype.Service;
import org.springframework.web.util.UriUtils;
import retrofit2.Response;

import java.io.InputStream;
import java.nio.charset.StandardCharsets;

@Service
public class StaticResourcesService {

public static final String DELIMITER = "/";
private final PipelineAuthManager authManager;
private final StaticResourcesClient client;
private final PipelineDataStorageClient storageClient;
private final CloudPipelineApiExecutor apiExecutor;

public StaticResourcesService(final PipelineAuthManager authManager,
final CloudPipelineApiBuilder builder,
final CloudPipelineApiExecutor apiExecutor) {
this.authManager = authManager;
this.client = builder.getClient(StaticResourcesClient.class);
this.storageClient = builder.getClient(PipelineDataStorageClient.class);
this.apiExecutor = apiExecutor;
}

@SneakyThrows
public InputStream getContent(final String path) {
public Response<ResponseBody> getContent(final String path) {
try {
return apiExecutor.getResponseStream(client.getContent(
UriUtils.encodePath(path, StandardCharsets.UTF_8.displayName()), authManager.getHeader()));
final String header = authManager.getHeader();
if (!path.endsWith(DELIMITER)) {
validateStorageItem(path, header);
}
return apiExecutor.getResponse(client.getContent(
UriUtils.encodePath(path, StandardCharsets.UTF_8.displayName()), header));
} catch (ObjectNotFoundException e) {
throw new ResourceNotFoundException(String.format("Storage path '%s' does not exist.", path));
}
}

private void validateStorageItem(final String requestPath, final String header) {
final AbstractDataStorage storage = apiExecutor.execute(storageClient.getStorage(requestPath, header));
final String[] split = requestPath.split(DELIMITER, 2);
final String path = split.length == 1 ? "" : split[1];
final DataStorageItemType type = apiExecutor.execute(storageClient.getItemType(storage.getId(), path, header));
if (type == DataStorageItemType.Folder && !requestPath.endsWith(DELIMITER)) {
throw new InvalidPathException("Folder path shall end with slash.");
}
}
}