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 Relative paths handling
  • Loading branch information
mzueva committed Nov 9, 2022
commit 68553c4698fb1744ee6395f32907376ea3a709de
Original file line number Diff line number Diff line change
Expand Up @@ -830,8 +830,9 @@ public final class MessageConstants {
"error.ngs.preprocessing.sampleid.is.not.found";

// Static resource
public static final String ERROR_STATIC_RESOURCES_INVALID_PATH =
"error.static.resources.invalid.path";
public static final String ERROR_STATIC_RESOURCES_INVALID_PATH = "error.static.resources.invalid.path";
public static final String ERROR_STATIC_RESOURCES_FOLDER_PATH = "error.static.resources.folder.path";


private MessageConstants() {
// no-op
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
import com.epam.pipeline.acl.resource.StaticResourceApiService;
import com.epam.pipeline.controller.AbstractRestController;
import com.epam.pipeline.entity.datastorage.DataStorageStreamingContent;
import com.epam.pipeline.exception.InvalidPathException;
import com.epam.pipeline.manager.datastorage.providers.ProviderUtils;
import com.epam.pipeline.manager.preference.PreferenceManager;
import com.epam.pipeline.manager.preference.SystemPreferences;
import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import org.apache.commons.collections4.MapUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
Expand All @@ -44,19 +47,26 @@
public class StaticResourcesController extends AbstractRestController {

private static final FileNameMap FILE_NAME_MAP = URLConnection.getFileNameMap();
public static final String STATIC_RESOURCES = "/static-resources/";
private static final String STATIC_RESOURCES = "/static-resources/";

private final StaticResourceApiService resourcesService;
private final PreferenceManager preferenceManager;

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

writeStreamToResponse(response, content.getContent(), fileName, mediaType,
!MediaType.APPLICATION_OCTET_STREAM.equals(mediaType), getCustomHeaders(fileName));
} catch (InvalidPathException e) {
response.setHeader("Location", request.getRequestURI() + ProviderUtils.DELIMITER);
response.setStatus(HttpStatus.FOUND.value());
}
}

private Map<String, String> getCustomHeaders(final String fileName) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright 2022 EPAM Systems, Inc. (https://www.epam.com/)
*
* 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 com.epam.pipeline.exception;

public class InvalidPathException extends RuntimeException {

public InvalidPathException(String message) {
super(message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,6 @@ public class SystemPreferences {
private static final String MONITORING_GROUP = "Monitoring";
private static final String CLOUD = "Cloud";
private static final String CLOUD_REGION_GROUP = "Cloud region";
private static final String STATIC_RESOURCES_GROUP = "Static Resources";

private static final String STORAGE_FSBROWSER_BLACK_LIST_DEFAULT =
"/bin,/var,/home,/root,/sbin,/sys,/usr,/boot,/dev,/lib,/proc,/etc";
Expand Down Expand Up @@ -716,6 +715,9 @@ public class SystemPreferences {
new ObjectPreference<>("data.sharing.static.resource.headers", null,
new TypeReference<Map<String, Map<String, String>>>() {}, DATA_SHARING_GROUP,
isNullOrValidJson(new TypeReference<Map<String, Map<String, String>>>() {}));
public static final StringPreference STATIC_RESOURCES_FOLDER_TEMPLATE_PATH =
new StringPreference("data.sharing.static.resource.template.path", "classpath:views/folder.vm",
DATA_SHARING_GROUP, pass);

// SYSTEM_GROUP
/**
Expand Down Expand Up @@ -1090,11 +1092,6 @@ public class SystemPreferences {
new TypeReference<List<CloudAccessManagementConfig>>() {}, CLOUD,
isNullOrValidJson(new TypeReference<List<CloudAccessManagementConfig>>() {}));

// Static Resources Group
public static final StringPreference STATIC_RESOURCES_FOLDER_TEMPLATE_PATH =
new StringPreference("static.resources.folder.template.path", "classpath:/views/folder.vm",
STATIC_RESOURCES_GROUP, pass);

private static final Pattern GIT_VERSION_PATTERN = Pattern.compile("(\\d)\\.(\\d)");

private static final Map<String, AbstractSystemPreference<?>> PREFERENCE_MAP;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,9 @@
import com.epam.pipeline.entity.datastorage.DataStorageFile;
import com.epam.pipeline.entity.datastorage.DataStorageItemType;
import com.epam.pipeline.entity.datastorage.DataStorageStreamingContent;
import com.epam.pipeline.exception.InvalidPathException;
import com.epam.pipeline.manager.datastorage.DataStorageManager;
import com.epam.pipeline.manager.datastorage.providers.ProviderUtils;
import com.epam.pipeline.manager.preference.PreferenceManager;
import com.epam.pipeline.manager.preference.SystemPreferences;
import lombok.RequiredArgsConstructor;
Expand All @@ -45,47 +47,46 @@
import java.util.List;
import java.util.stream.Collectors;

import static com.epam.pipeline.controller.resource.StaticResourcesController.STATIC_RESOURCES;

@Service
@RequiredArgsConstructor
public class StaticResourcesService {

private static final String DELIMITER = "/";
private static final String HTML = ".html";

private final DataStorageManager dataStorageManager;
private final MessageHelper messageHelper;
private final PreferenceManager preferenceManager;

@SneakyThrows
public DataStorageStreamingContent getContent(final String path) {
Assert.isTrue(StringUtils.isNotBlank(path) && path.contains(DELIMITER),
public DataStorageStreamingContent getContent(final String requestPath) {
Assert.isTrue(StringUtils.isNotBlank(requestPath),
messageHelper.getMessage(MessageConstants.ERROR_STATIC_RESOURCES_INVALID_PATH));
final String[] split = path.split(DELIMITER, 2);
final String[] split = requestPath.split(ProviderUtils.DELIMITER, 2);
final String bucketName = split[0];
final String filePath = split[1];
Assert.isTrue(StringUtils.isNotBlank(filePath),
messageHelper.getMessage(MessageConstants.ERROR_STATIC_RESOURCES_INVALID_PATH));
final String filePath = getFilePath(split);
final AbstractDataStorage storage = dataStorageManager.loadByNameOrId(bucketName);
final DataStorageItemType itemType = dataStorageManager.getItemType(storage, filePath, null);
if (itemType == DataStorageItemType.Folder) {
if (!requestPath.endsWith(ProviderUtils.DELIMITER)) {
throw new InvalidPathException(
messageHelper.getMessage(MessageConstants.ERROR_STATIC_RESOURCES_FOLDER_PATH));
}
final List<AbstractDataStorageItem> items = dataStorageManager.getDataStorageItems(storage.getId(),
filePath, false, null, null).getResults();
ProviderUtils.withTrailingDelimiter(filePath), false, null, null).getResults();
final String templatePath = preferenceManager.getPreference(
SystemPreferences.STATIC_RESOURCES_FOLDER_TEMPLATE_PATH);
final String staticResourcesPrefix = preferenceManager.getPreference(SystemPreferences.BASE_API_HOST)
+ STATIC_RESOURCES;
final String html = buildHtml(items, templatePath, staticResourcesPrefix);
return new DataStorageStreamingContent(new ByteArrayInputStream(html.getBytes()), filePath);
final String html = buildHtml(items, templatePath, filePath);
return new DataStorageStreamingContent(new ByteArrayInputStream(html.getBytes()),
ProviderUtils.withoutTrailingDelimiter(filePath) + HTML);
}
return dataStorageManager.getStreamingContent(storage.getId(), filePath, null);
}

public static String buildHtml(final List<AbstractDataStorageItem> items,
final String templatePath,
final String staticResourcesPrefix) throws IOException{
final String templatePath,
final String folder) throws IOException {
final VelocityContext velocityContext = new VelocityContext();
velocityContext.put("items", getHtmlStorageItems(items, staticResourcesPrefix));
velocityContext.put("items", getHtmlStorageItems(items, folder));
final String template = IOUtils.toString(new FileReader(ResourceUtils.getFile(templatePath)));
try (StringWriter out = new StringWriter()) {
Velocity.evaluate(velocityContext, out, "folder", template);
Expand All @@ -94,11 +95,11 @@ public static String buildHtml(final List<AbstractDataStorageItem> items,
}

private static List<HtmlStorageItem> getHtmlStorageItems(final List<AbstractDataStorageItem> items,
final String staticResourcesPrefix) {
final String folder) {
return items.stream()
.map(i -> HtmlStorageItem.builder()
.name(i.getName())
.path(staticResourcesPrefix + i.getPath())
.path(i.getPath().replaceFirst(folder, ""))
.size(getFileSize(i))
.type(i.getType())
.changed(i.getType() == DataStorageItemType.File ? ((DataStorageFile) i).getChanged() : "")
Expand All @@ -111,4 +112,8 @@ private static String getFileSize(final AbstractDataStorageItem item) {
return item.getType() == DataStorageItemType.File ?
FileUtils.byteCountToDisplaySize(((DataStorageFile) item).getSize()) : "";
}

private String getFilePath(final String[] chunks) {
return chunks.length == 1 ? "" : chunks[1];
}
}
1 change: 1 addition & 0 deletions api/src/main/resources/messages.properties
Original file line number Diff line number Diff line change
Expand Up @@ -720,3 +720,4 @@ error.ngs.preprocessing.sampleid.is.not.found=Sample_ID column is not found in s

# Static resources
error.static.resources.invalid.path=Invalid path specified to request static resource.
error.static.resources.folder.path=Folder path shall end with a slash.
7 changes: 5 additions & 2 deletions api/src/main/resources/views/folder.vm
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,16 @@
</tr>
</thead>
<tbody>
<tr>
<td align="left"> <a class="icon dir" href="../">..</a> </td>
</tr>
#foreach($item in $items)
<tr>
<td align="left">
#if($item.type == "Folder")
<a class="icon dir" href="${item.path}">${item.name}</a>
<a class="icon dir" href="./${item.path}/">${item.name}</a>
#else
<a class="icon file" href="${item.path}">${item.name}</a>
<a class="icon file" href="./${item.path}">${item.name}</a>
#end
</td>
<td class="detailsColumn">${item.size}</td>
Expand Down