From c88c1ce4bcace2307ec4c1ef2691623400fd28f3 Mon Sep 17 00:00:00 2001 From: XingY Date: Fri, 17 Oct 2025 10:01:11 -0700 Subject: [PATCH 1/2] Export file value with its relative folder path so it can be re-imported correct --- .../study/assay/FileLinkDisplayColumn.java | 948 +++++++++--------- 1 file changed, 477 insertions(+), 471 deletions(-) diff --git a/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java b/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java index 87085810199..a2b5fef931d 100644 --- a/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java +++ b/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java @@ -1,471 +1,477 @@ -/* - * Copyright (c) 2008-2019 LabKey Corporation - * - * 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.labkey.api.study.assay; - -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.admin.CoreUrls; -import org.labkey.api.attachments.Attachment; -import org.labkey.api.data.AbstractFileDisplayColumn; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerManager; -import org.labkey.api.data.DisplayColumn; -import org.labkey.api.data.RemappingDisplayColumnFactory; -import org.labkey.api.data.RenderContext; -import org.labkey.api.data.TableViewForm; -import org.labkey.api.exp.PropertyDescriptor; -import org.labkey.api.files.FileContentService; -import org.labkey.api.pipeline.PipeRoot; -import org.labkey.api.pipeline.PipelineService; -import org.labkey.api.query.DetailsURL; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.SchemaKey; -import org.labkey.api.security.User; -import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.api.settings.AppProps; -import org.labkey.api.util.ContainerContext; -import org.labkey.api.util.FileUtil; -import org.labkey.api.util.NetworkDrive; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.util.Path; -import org.labkey.api.util.URIUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.api.webdav.WebdavResource; -import org.labkey.api.webdav.WebdavService; -import org.labkey.api.writer.HtmlWriter; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class FileLinkDisplayColumn extends AbstractFileDisplayColumn -{ - // Issue 46282 - let admins choose if files should be rendered inside browser or downloaded as files - public static final String AS_ATTACHMENT_FORMAT = "attachment"; - public static final String AS_INLINE_FORMAT = "inline"; - - public static class Factory implements RemappingDisplayColumnFactory - { - private final Container _container; - - private PropertyDescriptor _pd; - private DetailsURL _detailsUrl; - private SchemaKey _schemaKey; - private String _queryName; - private FieldKey _pkFieldKey; - private FieldKey _objectURIFieldKey; - - public Factory(PropertyDescriptor pd, Container c, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull FieldKey pkFieldKey) - { - _pd = pd; - _container = c; - _schemaKey = schemaKey; - _queryName = queryName; - _pkFieldKey = pkFieldKey; - } - - public Factory(PropertyDescriptor pd, Container c, @NotNull FieldKey lsidColumnFieldKey) - { - _pd = pd; - _container = c; - _objectURIFieldKey = lsidColumnFieldKey; - } - - public Factory(DetailsURL detailsURL, Container c, @NotNull FieldKey pkFieldKey) - { - _detailsUrl = detailsURL; - _container = c; - _pkFieldKey = pkFieldKey; - } - - @Override - public Factory remapFieldKeys(@Nullable FieldKey parent, @Nullable Map remap) - { - Factory remapped = this.clone(); - if (remapped._pkFieldKey != null) - { - remapped._pkFieldKey = FieldKey.remap(_pkFieldKey, parent, remap); - if (null == remapped._pkFieldKey) - remapped._pkFieldKey = _pkFieldKey; - } - if (remapped._objectURIFieldKey != null) - { - remapped._objectURIFieldKey = FieldKey.remap(_objectURIFieldKey, parent, remap); - if (null == remapped._objectURIFieldKey) - remapped._objectURIFieldKey = _objectURIFieldKey; - } - return remapped; - } - - @Override - public DisplayColumn createRenderer(ColumnInfo col) - { - if (_pd == null && _detailsUrl != null) - return new FileLinkDisplayColumn(col, _detailsUrl, _container, _pkFieldKey); - else if (_pkFieldKey != null) - return new FileLinkDisplayColumn(col, _pd, _container, _schemaKey, _queryName, _pkFieldKey); - else if (_container != null) - return new FileLinkDisplayColumn(col, _pd, _container, _objectURIFieldKey); - else - throw new IllegalArgumentException("Cannot create a renderer from the specified configuration properties"); - } - - @Override - public FileLinkDisplayColumn.Factory clone() - { - try - { - return (Factory)super.clone(); - } - catch (CloneNotSupportedException e) - { - throw new RuntimeException(e); - } - } - } - - private final Container _container; - - private FieldKey _pkFieldKey; - private FieldKey _objectURIFieldKey; - - /** Use schemaName/queryName and pk FieldKey value to resolve File in CoreController.DownloadFileLinkAction. */ - public FileLinkDisplayColumn(ColumnInfo col, PropertyDescriptor pd, Container container, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull FieldKey pkFieldKey) - { - super(col); - _container = container; - _pkFieldKey = pkFieldKey; - - if (pd.getURL() == null) - { - // Don't stomp over an explicitly configured URL on this column - StringBuilder sb = new StringBuilder("/core/downloadFileLink.view?propertyId="); - sb.append(pd.getPropertyId()); - sb.append("&schemaName="); - sb.append(PageFlowUtil.encodeURIComponent(schemaKey.toString())); - sb.append("&queryName="); - sb.append(PageFlowUtil.encodeURIComponent(queryName)); - sb.append("&pk=${"); - sb.append(pkFieldKey); - sb.append("}"); - sb.append("&modified=${Modified}"); - if (AS_ATTACHMENT_FORMAT.equalsIgnoreCase(col.getFormat())) - { - sb.append("&inline=false"); - } - ContainerContext context = new ContainerContext.FieldKeyContext(new FieldKey(pkFieldKey.getParent(), "Folder")); - setURLExpression(DetailsURL.fromString(sb.toString(), context)); - } - } - - /** Use LSID FieldKey value as ObjectURI to resolve File in CoreController.DownloadFileLinkAction. */ - public FileLinkDisplayColumn(ColumnInfo col, PropertyDescriptor pd, Container container, @NotNull FieldKey objectURIFieldKey) - { - super(col); - _container = container; - _objectURIFieldKey = objectURIFieldKey; - - if (pd.getURL() == null) - { - // Don't stomp over an explicitly configured URL on this column - - ActionURL baseUrl = PageFlowUtil.urlProvider(CoreUrls.class).getDownloadFileLinkBaseURL(container, pd); - if (AS_ATTACHMENT_FORMAT.equalsIgnoreCase(col.getFormat())) - { - baseUrl.addParameter("inline", "false"); - } - else - { - setLinkTarget("_blank"); - } - DetailsURL url = new DetailsURL(baseUrl, "objectURI", objectURIFieldKey); - setURLExpression(url); - } - } - - public FileLinkDisplayColumn(ColumnInfo col, DetailsURL detailsURL, Container container, @NotNull FieldKey pkFieldKey) - { - super(col); - _container = container; - _pkFieldKey = pkFieldKey; - - setURLExpression(detailsURL); - } - - @Override - protected Object getInputValue(RenderContext ctx) - { - ColumnInfo col = getColumnInfo(); - Object val = null; - TableViewForm viewForm = ctx.getForm(); - - if (col != null) - { - if (null != viewForm && viewForm.contains(this, ctx)) - { - val = viewForm.get(getFormFieldName(ctx)); - } - else if (ctx.getRow() != null) - val = col.getValue(ctx); - } - - return val; - } - - @Override - public void addQueryFieldKeys(Set keys) - { - super.addQueryFieldKeys(keys); - keys.add(FieldKey.fromParts("Modified")); - if (_pkFieldKey != null) - keys.add(_pkFieldKey); - if (_objectURIFieldKey != null) - keys.add(_objectURIFieldKey); - } - - public static boolean filePathExist(String path, Container container, User user) - { - String davPath = path; - if (FileUtil.isUrlEncoded(davPath)) - davPath = FileUtil.decodeURL(davPath); - var resolver = WebdavService.get().getResolver(); - // Resolve path under webdav root - Path parsed = Path.parse(StringUtils.trim(davPath)); - - // Issue 52968: handle context path - Path contextPath = AppProps.getInstance().getParsedContextPath(); - if (parsed.startsWith(contextPath)) - parsed = parsed.subpath(contextPath.size(), parsed.size()); - - WebdavResource resource = resolver.lookup(parsed); - if ((null == resource || !resource.exists()) && !parsed.startsWith(new Path("_webdav"))) - resource = resolver.lookup(new Path("_webdav").append(parsed)); - if (resource != null && resource.isFile() && resource.canRead(user, true)) - { - return true; - } - else - { - // Resolve file under pipeline root - PipeRoot root = PipelineService.get().findPipelineRoot(container); - if (root != null) - { - // Attempt absolute path first, then relative path from pipeline root - File f = new File(path); - if (!root.isUnderRoot(f)) - f = root.resolvePath(path); - - return (NetworkDrive.exists(f) && root.isUnderRoot(f) && root.hasPermission(container, user, ReadPermission.class)); - } - } - - return false; - } - - @Override - protected String getFileName(RenderContext ctx, Object value) - { - return getFileName(ctx, value, false); - } - - @Override - protected String getFileName(RenderContext ctx, Object value, boolean isDisplay) - { - String result = value == null ? null : StringUtils.trimToNull(value.toString()); - if (result != null) - { - File f = null; - if (result.startsWith("file:")) - { - try - { - f = new File(new URI(result)); - } - catch (URISyntaxException x) - { - // try to recover - result = result.substring("file:".length()); - } - } - if (null == f) - f = FileUtil.getAbsoluteCaseSensitiveFile(new File(result)); - NetworkDrive.ensureDrive(f.getPath()); - List fileRootTypes = List.of(FileContentService.ContentType.files, FileContentService.ContentType.pipeline, FileContentService.ContentType.assayfiles); - boolean valid = false; - List containers = new ArrayList<>(); - containers.add(_container); - // Not ideal, but needed in case data is queried from cross folder context - if (ctx.get("folder") != null || ctx.get("container") != null) - { - Object folderObj = ctx.get("folder"); - if (folderObj == null) - folderObj = ctx.get("container"); - if (folderObj instanceof String containerId) - { - Container dataContainer = ContainerManager.getForId(containerId); - if (dataContainer != null && !dataContainer.equals(_container)) - containers.add(dataContainer); - } - } - for (Container container : containers) - { - if (valid) - break; - - for (FileContentService.ContentType fileRootType : fileRootTypes) - { - result = relativize(f, FileContentService.get().getFileRoot(container, fileRootType)); - if (result != null) - { - // Issue 54062: Strip folder name from displayed name - if (isDisplay) - result = f.getName(); - - valid = true; - break; - } - } - } - if (result == null) - { - result = f.getName(); - } - - if ((!valid || !f.exists()) && !result.endsWith(UNAVAILABLE_FILE_SUFFIX)) - result += UNAVAILABLE_FILE_SUFFIX; - } - return result; - } - - public static String relativize(File f, File fileRoot) - { - if (fileRoot != null) - { - NetworkDrive.ensureDrive(fileRoot.getPath()); - fileRoot = FileUtil.getAbsoluteCaseSensitiveFile(fileRoot); - if (URIUtil.isDescendant(fileRoot.toURI(), f.toURI())) - { - try - { - return FileUtil.relativize(fileRoot, f, false); - } - catch (IOException ignored) {} - } - } - return null; - } - - @Override - protected InputStream getFileContents(RenderContext ctx, Object ignore) throws FileNotFoundException - { - Object value = getValue(ctx); - String s = value == null ? null : StringUtils.trimToNull(value.toString()); - if (s != null) - { - File f = new File(s); - if (f.isFile()) - return new FileInputStream(f); - } - return null; - } - - @Override - protected void renderIconAndFilename( - RenderContext ctx, - HtmlWriter out, - String fileValue /*Could be raw path value, or processed filename by `getFileName`*/, - boolean link, - boolean thumbnail) - { - Object value = getValue(ctx); - String strValue = value == null ? null : StringUtils.trimToNull(value.toString()); - if (strValue != null && !fileValue.endsWith(UNAVAILABLE_FILE_SUFFIX)) - { - File f; - if (strValue.startsWith("file:")) - f = new File(URI.create(strValue)); - else - f = new File(strValue); - - if (!f.exists()) - { - // try all file root - List fileRootTypes = List.of(FileContentService.ContentType.files, FileContentService.ContentType.pipeline, FileContentService.ContentType.assayfiles); - for (FileContentService.ContentType fileRootType : fileRootTypes) - { - String fullPath = FileContentService.get().getFileRoot(_container, fileRootType).getAbsolutePath()+ File.separator + value; - f = new File(fullPath); - if (f.exists()) - break; - } - } - - // It's probably a file, so check that first - if (f.isFile()) - { - super.renderIconAndFilename(ctx, out, strValue, link, thumbnail); - } - else if (f.isDirectory()) - { - super.renderIconAndFilename(ctx, out, strValue, Attachment.getFileIcon(".folder"), null, link, false); - } - else - { - // It's not on the file system anymore, so don't offer a link and tell the user it's unavailable - super.renderIconAndFilename(ctx, out, strValue, Attachment.getFileIcon(fileValue), null, false, false); - } - } - else - { - super.renderIconAndFilename(ctx, out, fileValue, link, thumbnail); - } - } - - @Override - public Object getDisplayValue(RenderContext ctx) - { - return getFileName(ctx, super.getDisplayValue(ctx), true); - } - - @Override - public Object getJsonValue(RenderContext ctx) - { - return getFileName(ctx, super.getDisplayValue(ctx)); - } - - @Override - public boolean isFilterable() - { - return false; - } - @Override - public boolean isSortable() - { - return false; - } - -} +/* + * Copyright (c) 2008-2019 LabKey Corporation + * + * 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.labkey.api.study.assay; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.admin.CoreUrls; +import org.labkey.api.attachments.Attachment; +import org.labkey.api.data.AbstractFileDisplayColumn; +import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerManager; +import org.labkey.api.data.DisplayColumn; +import org.labkey.api.data.RemappingDisplayColumnFactory; +import org.labkey.api.data.RenderContext; +import org.labkey.api.data.TableViewForm; +import org.labkey.api.exp.PropertyDescriptor; +import org.labkey.api.files.FileContentService; +import org.labkey.api.pipeline.PipeRoot; +import org.labkey.api.pipeline.PipelineService; +import org.labkey.api.query.DetailsURL; +import org.labkey.api.query.FieldKey; +import org.labkey.api.query.SchemaKey; +import org.labkey.api.security.User; +import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.settings.AppProps; +import org.labkey.api.util.ContainerContext; +import org.labkey.api.util.FileUtil; +import org.labkey.api.util.NetworkDrive; +import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.util.Path; +import org.labkey.api.util.URIUtil; +import org.labkey.api.view.ActionURL; +import org.labkey.api.webdav.WebdavResource; +import org.labkey.api.webdav.WebdavService; +import org.labkey.api.writer.HtmlWriter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class FileLinkDisplayColumn extends AbstractFileDisplayColumn +{ + // Issue 46282 - let admins choose if files should be rendered inside browser or downloaded as files + public static final String AS_ATTACHMENT_FORMAT = "attachment"; + public static final String AS_INLINE_FORMAT = "inline"; + + public static class Factory implements RemappingDisplayColumnFactory + { + private final Container _container; + + private PropertyDescriptor _pd; + private DetailsURL _detailsUrl; + private SchemaKey _schemaKey; + private String _queryName; + private FieldKey _pkFieldKey; + private FieldKey _objectURIFieldKey; + + public Factory(PropertyDescriptor pd, Container c, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull FieldKey pkFieldKey) + { + _pd = pd; + _container = c; + _schemaKey = schemaKey; + _queryName = queryName; + _pkFieldKey = pkFieldKey; + } + + public Factory(PropertyDescriptor pd, Container c, @NotNull FieldKey lsidColumnFieldKey) + { + _pd = pd; + _container = c; + _objectURIFieldKey = lsidColumnFieldKey; + } + + public Factory(DetailsURL detailsURL, Container c, @NotNull FieldKey pkFieldKey) + { + _detailsUrl = detailsURL; + _container = c; + _pkFieldKey = pkFieldKey; + } + + @Override + public Factory remapFieldKeys(@Nullable FieldKey parent, @Nullable Map remap) + { + Factory remapped = this.clone(); + if (remapped._pkFieldKey != null) + { + remapped._pkFieldKey = FieldKey.remap(_pkFieldKey, parent, remap); + if (null == remapped._pkFieldKey) + remapped._pkFieldKey = _pkFieldKey; + } + if (remapped._objectURIFieldKey != null) + { + remapped._objectURIFieldKey = FieldKey.remap(_objectURIFieldKey, parent, remap); + if (null == remapped._objectURIFieldKey) + remapped._objectURIFieldKey = _objectURIFieldKey; + } + return remapped; + } + + @Override + public DisplayColumn createRenderer(ColumnInfo col) + { + if (_pd == null && _detailsUrl != null) + return new FileLinkDisplayColumn(col, _detailsUrl, _container, _pkFieldKey); + else if (_pkFieldKey != null) + return new FileLinkDisplayColumn(col, _pd, _container, _schemaKey, _queryName, _pkFieldKey); + else if (_container != null) + return new FileLinkDisplayColumn(col, _pd, _container, _objectURIFieldKey); + else + throw new IllegalArgumentException("Cannot create a renderer from the specified configuration properties"); + } + + @Override + public FileLinkDisplayColumn.Factory clone() + { + try + { + return (Factory)super.clone(); + } + catch (CloneNotSupportedException e) + { + throw new RuntimeException(e); + } + } + } + + private final Container _container; + + private FieldKey _pkFieldKey; + private FieldKey _objectURIFieldKey; + + /** Use schemaName/queryName and pk FieldKey value to resolve File in CoreController.DownloadFileLinkAction. */ + public FileLinkDisplayColumn(ColumnInfo col, PropertyDescriptor pd, Container container, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull FieldKey pkFieldKey) + { + super(col); + _container = container; + _pkFieldKey = pkFieldKey; + + if (pd.getURL() == null) + { + // Don't stomp over an explicitly configured URL on this column + StringBuilder sb = new StringBuilder("/core/downloadFileLink.view?propertyId="); + sb.append(pd.getPropertyId()); + sb.append("&schemaName="); + sb.append(PageFlowUtil.encodeURIComponent(schemaKey.toString())); + sb.append("&queryName="); + sb.append(PageFlowUtil.encodeURIComponent(queryName)); + sb.append("&pk=${"); + sb.append(pkFieldKey); + sb.append("}"); + sb.append("&modified=${Modified}"); + if (AS_ATTACHMENT_FORMAT.equalsIgnoreCase(col.getFormat())) + { + sb.append("&inline=false"); + } + ContainerContext context = new ContainerContext.FieldKeyContext(new FieldKey(pkFieldKey.getParent(), "Folder")); + setURLExpression(DetailsURL.fromString(sb.toString(), context)); + } + } + + /** Use LSID FieldKey value as ObjectURI to resolve File in CoreController.DownloadFileLinkAction. */ + public FileLinkDisplayColumn(ColumnInfo col, PropertyDescriptor pd, Container container, @NotNull FieldKey objectURIFieldKey) + { + super(col); + _container = container; + _objectURIFieldKey = objectURIFieldKey; + + if (pd.getURL() == null) + { + // Don't stomp over an explicitly configured URL on this column + + ActionURL baseUrl = PageFlowUtil.urlProvider(CoreUrls.class).getDownloadFileLinkBaseURL(container, pd); + if (AS_ATTACHMENT_FORMAT.equalsIgnoreCase(col.getFormat())) + { + baseUrl.addParameter("inline", "false"); + } + else + { + setLinkTarget("_blank"); + } + DetailsURL url = new DetailsURL(baseUrl, "objectURI", objectURIFieldKey); + setURLExpression(url); + } + } + + public FileLinkDisplayColumn(ColumnInfo col, DetailsURL detailsURL, Container container, @NotNull FieldKey pkFieldKey) + { + super(col); + _container = container; + _pkFieldKey = pkFieldKey; + + setURLExpression(detailsURL); + } + + @Override + protected Object getInputValue(RenderContext ctx) + { + ColumnInfo col = getColumnInfo(); + Object val = null; + TableViewForm viewForm = ctx.getForm(); + + if (col != null) + { + if (null != viewForm && viewForm.contains(this, ctx)) + { + val = viewForm.get(getFormFieldName(ctx)); + } + else if (ctx.getRow() != null) + val = col.getValue(ctx); + } + + return val; + } + + @Override + public void addQueryFieldKeys(Set keys) + { + super.addQueryFieldKeys(keys); + keys.add(FieldKey.fromParts("Modified")); + if (_pkFieldKey != null) + keys.add(_pkFieldKey); + if (_objectURIFieldKey != null) + keys.add(_objectURIFieldKey); + } + + public static boolean filePathExist(String path, Container container, User user) + { + String davPath = path; + if (FileUtil.isUrlEncoded(davPath)) + davPath = FileUtil.decodeURL(davPath); + var resolver = WebdavService.get().getResolver(); + // Resolve path under webdav root + Path parsed = Path.parse(StringUtils.trim(davPath)); + + // Issue 52968: handle context path + Path contextPath = AppProps.getInstance().getParsedContextPath(); + if (parsed.startsWith(contextPath)) + parsed = parsed.subpath(contextPath.size(), parsed.size()); + + WebdavResource resource = resolver.lookup(parsed); + if ((null == resource || !resource.exists()) && !parsed.startsWith(new Path("_webdav"))) + resource = resolver.lookup(new Path("_webdav").append(parsed)); + if (resource != null && resource.isFile() && resource.canRead(user, true)) + { + return true; + } + else + { + // Resolve file under pipeline root + PipeRoot root = PipelineService.get().findPipelineRoot(container); + if (root != null) + { + // Attempt absolute path first, then relative path from pipeline root + File f = new File(path); + if (!root.isUnderRoot(f)) + f = root.resolvePath(path); + + return (NetworkDrive.exists(f) && root.isUnderRoot(f) && root.hasPermission(container, user, ReadPermission.class)); + } + } + + return false; + } + + @Override + protected String getFileName(RenderContext ctx, Object value) + { + return getFileName(ctx, value, false); + } + + @Override + protected String getFileName(RenderContext ctx, Object value, boolean isDisplay) + { + String result = value == null ? null : StringUtils.trimToNull(value.toString()); + if (result != null) + { + File f = null; + if (result.startsWith("file:")) + { + try + { + f = new File(new URI(result)); + } + catch (URISyntaxException x) + { + // try to recover + result = result.substring("file:".length()); + } + } + if (null == f) + f = FileUtil.getAbsoluteCaseSensitiveFile(new File(result)); + NetworkDrive.ensureDrive(f.getPath()); + List fileRootTypes = List.of(FileContentService.ContentType.files, FileContentService.ContentType.pipeline, FileContentService.ContentType.assayfiles); + boolean valid = false; + List containers = new ArrayList<>(); + containers.add(_container); + // Not ideal, but needed in case data is queried from cross folder context + if (ctx.get("folder") != null || ctx.get("container") != null) + { + Object folderObj = ctx.get("folder"); + if (folderObj == null) + folderObj = ctx.get("container"); + if (folderObj instanceof String containerId) + { + Container dataContainer = ContainerManager.getForId(containerId); + if (dataContainer != null && !dataContainer.equals(_container)) + containers.add(dataContainer); + } + } + for (Container container : containers) + { + if (valid) + break; + + for (FileContentService.ContentType fileRootType : fileRootTypes) + { + result = relativize(f, FileContentService.get().getFileRoot(container, fileRootType)); + if (result != null) + { + // Issue 54062: Strip folder name from displayed name + if (isDisplay) + result = f.getName(); + + valid = true; + break; + } + } + } + if (result == null) + { + result = f.getName(); + } + + if ((!valid || !f.exists()) && !result.endsWith(UNAVAILABLE_FILE_SUFFIX)) + result += UNAVAILABLE_FILE_SUFFIX; + } + return result; + } + + public static String relativize(File f, File fileRoot) + { + if (fileRoot != null) + { + NetworkDrive.ensureDrive(fileRoot.getPath()); + fileRoot = FileUtil.getAbsoluteCaseSensitiveFile(fileRoot); + if (URIUtil.isDescendant(fileRoot.toURI(), f.toURI())) + { + try + { + return FileUtil.relativize(fileRoot, f, false); + } + catch (IOException ignored) {} + } + } + return null; + } + + @Override + protected InputStream getFileContents(RenderContext ctx, Object ignore) throws FileNotFoundException + { + Object value = getValue(ctx); + String s = value == null ? null : StringUtils.trimToNull(value.toString()); + if (s != null) + { + File f = new File(s); + if (f.isFile()) + return new FileInputStream(f); + } + return null; + } + + @Override + protected void renderIconAndFilename( + RenderContext ctx, + HtmlWriter out, + String fileValue /*Could be raw path value, or processed filename by `getFileName`*/, + boolean link, + boolean thumbnail) + { + Object value = getValue(ctx); + String strValue = value == null ? null : StringUtils.trimToNull(value.toString()); + if (strValue != null && !fileValue.endsWith(UNAVAILABLE_FILE_SUFFIX)) + { + File f; + if (strValue.startsWith("file:")) + f = new File(URI.create(strValue)); + else + f = new File(strValue); + + if (!f.exists()) + { + // try all file root + List fileRootTypes = List.of(FileContentService.ContentType.files, FileContentService.ContentType.pipeline, FileContentService.ContentType.assayfiles); + for (FileContentService.ContentType fileRootType : fileRootTypes) + { + String fullPath = FileContentService.get().getFileRoot(_container, fileRootType).getAbsolutePath()+ File.separator + value; + f = new File(fullPath); + if (f.exists()) + break; + } + } + + // It's probably a file, so check that first + if (f.isFile()) + { + super.renderIconAndFilename(ctx, out, strValue, link, thumbnail); + } + else if (f.isDirectory()) + { + super.renderIconAndFilename(ctx, out, strValue, Attachment.getFileIcon(".folder"), null, link, false); + } + else + { + // It's not on the file system anymore, so don't offer a link and tell the user it's unavailable + super.renderIconAndFilename(ctx, out, strValue, Attachment.getFileIcon(fileValue), null, false, false); + } + } + else + { + super.renderIconAndFilename(ctx, out, fileValue, link, thumbnail); + } + } + + @Override + public Object getDisplayValue(RenderContext ctx) + { + return getFileName(ctx, super.getDisplayValue(ctx), true); + } + + @Override + public Object getJsonValue(RenderContext ctx) + { + return getFileName(ctx, super.getDisplayValue(ctx)); + } + + @Override + public Object getExportCompatibleValue(RenderContext ctx) + { + return getJsonValue(ctx); + } + + @Override + public boolean isFilterable() + { + return false; + } + @Override + public boolean isSortable() + { + return false; + } + +} From dcfc0ca4ab243acf33a6708006ea39d39a3deac0 Mon Sep 17 00:00:00 2001 From: XingY Date: Fri, 17 Oct 2025 10:04:38 -0700 Subject: [PATCH 2/2] crlf --- .../study/assay/FileLinkDisplayColumn.java | 954 +++++++++--------- 1 file changed, 477 insertions(+), 477 deletions(-) diff --git a/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java b/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java index a2b5fef931d..06ae841347c 100644 --- a/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java +++ b/api/src/org/labkey/api/study/assay/FileLinkDisplayColumn.java @@ -1,477 +1,477 @@ -/* - * Copyright (c) 2008-2019 LabKey Corporation - * - * 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.labkey.api.study.assay; - -import org.apache.commons.lang3.StringUtils; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.labkey.api.admin.CoreUrls; -import org.labkey.api.attachments.Attachment; -import org.labkey.api.data.AbstractFileDisplayColumn; -import org.labkey.api.data.ColumnInfo; -import org.labkey.api.data.Container; -import org.labkey.api.data.ContainerManager; -import org.labkey.api.data.DisplayColumn; -import org.labkey.api.data.RemappingDisplayColumnFactory; -import org.labkey.api.data.RenderContext; -import org.labkey.api.data.TableViewForm; -import org.labkey.api.exp.PropertyDescriptor; -import org.labkey.api.files.FileContentService; -import org.labkey.api.pipeline.PipeRoot; -import org.labkey.api.pipeline.PipelineService; -import org.labkey.api.query.DetailsURL; -import org.labkey.api.query.FieldKey; -import org.labkey.api.query.SchemaKey; -import org.labkey.api.security.User; -import org.labkey.api.security.permissions.ReadPermission; -import org.labkey.api.settings.AppProps; -import org.labkey.api.util.ContainerContext; -import org.labkey.api.util.FileUtil; -import org.labkey.api.util.NetworkDrive; -import org.labkey.api.util.PageFlowUtil; -import org.labkey.api.util.Path; -import org.labkey.api.util.URIUtil; -import org.labkey.api.view.ActionURL; -import org.labkey.api.webdav.WebdavResource; -import org.labkey.api.webdav.WebdavService; -import org.labkey.api.writer.HtmlWriter; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; - -public class FileLinkDisplayColumn extends AbstractFileDisplayColumn -{ - // Issue 46282 - let admins choose if files should be rendered inside browser or downloaded as files - public static final String AS_ATTACHMENT_FORMAT = "attachment"; - public static final String AS_INLINE_FORMAT = "inline"; - - public static class Factory implements RemappingDisplayColumnFactory - { - private final Container _container; - - private PropertyDescriptor _pd; - private DetailsURL _detailsUrl; - private SchemaKey _schemaKey; - private String _queryName; - private FieldKey _pkFieldKey; - private FieldKey _objectURIFieldKey; - - public Factory(PropertyDescriptor pd, Container c, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull FieldKey pkFieldKey) - { - _pd = pd; - _container = c; - _schemaKey = schemaKey; - _queryName = queryName; - _pkFieldKey = pkFieldKey; - } - - public Factory(PropertyDescriptor pd, Container c, @NotNull FieldKey lsidColumnFieldKey) - { - _pd = pd; - _container = c; - _objectURIFieldKey = lsidColumnFieldKey; - } - - public Factory(DetailsURL detailsURL, Container c, @NotNull FieldKey pkFieldKey) - { - _detailsUrl = detailsURL; - _container = c; - _pkFieldKey = pkFieldKey; - } - - @Override - public Factory remapFieldKeys(@Nullable FieldKey parent, @Nullable Map remap) - { - Factory remapped = this.clone(); - if (remapped._pkFieldKey != null) - { - remapped._pkFieldKey = FieldKey.remap(_pkFieldKey, parent, remap); - if (null == remapped._pkFieldKey) - remapped._pkFieldKey = _pkFieldKey; - } - if (remapped._objectURIFieldKey != null) - { - remapped._objectURIFieldKey = FieldKey.remap(_objectURIFieldKey, parent, remap); - if (null == remapped._objectURIFieldKey) - remapped._objectURIFieldKey = _objectURIFieldKey; - } - return remapped; - } - - @Override - public DisplayColumn createRenderer(ColumnInfo col) - { - if (_pd == null && _detailsUrl != null) - return new FileLinkDisplayColumn(col, _detailsUrl, _container, _pkFieldKey); - else if (_pkFieldKey != null) - return new FileLinkDisplayColumn(col, _pd, _container, _schemaKey, _queryName, _pkFieldKey); - else if (_container != null) - return new FileLinkDisplayColumn(col, _pd, _container, _objectURIFieldKey); - else - throw new IllegalArgumentException("Cannot create a renderer from the specified configuration properties"); - } - - @Override - public FileLinkDisplayColumn.Factory clone() - { - try - { - return (Factory)super.clone(); - } - catch (CloneNotSupportedException e) - { - throw new RuntimeException(e); - } - } - } - - private final Container _container; - - private FieldKey _pkFieldKey; - private FieldKey _objectURIFieldKey; - - /** Use schemaName/queryName and pk FieldKey value to resolve File in CoreController.DownloadFileLinkAction. */ - public FileLinkDisplayColumn(ColumnInfo col, PropertyDescriptor pd, Container container, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull FieldKey pkFieldKey) - { - super(col); - _container = container; - _pkFieldKey = pkFieldKey; - - if (pd.getURL() == null) - { - // Don't stomp over an explicitly configured URL on this column - StringBuilder sb = new StringBuilder("/core/downloadFileLink.view?propertyId="); - sb.append(pd.getPropertyId()); - sb.append("&schemaName="); - sb.append(PageFlowUtil.encodeURIComponent(schemaKey.toString())); - sb.append("&queryName="); - sb.append(PageFlowUtil.encodeURIComponent(queryName)); - sb.append("&pk=${"); - sb.append(pkFieldKey); - sb.append("}"); - sb.append("&modified=${Modified}"); - if (AS_ATTACHMENT_FORMAT.equalsIgnoreCase(col.getFormat())) - { - sb.append("&inline=false"); - } - ContainerContext context = new ContainerContext.FieldKeyContext(new FieldKey(pkFieldKey.getParent(), "Folder")); - setURLExpression(DetailsURL.fromString(sb.toString(), context)); - } - } - - /** Use LSID FieldKey value as ObjectURI to resolve File in CoreController.DownloadFileLinkAction. */ - public FileLinkDisplayColumn(ColumnInfo col, PropertyDescriptor pd, Container container, @NotNull FieldKey objectURIFieldKey) - { - super(col); - _container = container; - _objectURIFieldKey = objectURIFieldKey; - - if (pd.getURL() == null) - { - // Don't stomp over an explicitly configured URL on this column - - ActionURL baseUrl = PageFlowUtil.urlProvider(CoreUrls.class).getDownloadFileLinkBaseURL(container, pd); - if (AS_ATTACHMENT_FORMAT.equalsIgnoreCase(col.getFormat())) - { - baseUrl.addParameter("inline", "false"); - } - else - { - setLinkTarget("_blank"); - } - DetailsURL url = new DetailsURL(baseUrl, "objectURI", objectURIFieldKey); - setURLExpression(url); - } - } - - public FileLinkDisplayColumn(ColumnInfo col, DetailsURL detailsURL, Container container, @NotNull FieldKey pkFieldKey) - { - super(col); - _container = container; - _pkFieldKey = pkFieldKey; - - setURLExpression(detailsURL); - } - - @Override - protected Object getInputValue(RenderContext ctx) - { - ColumnInfo col = getColumnInfo(); - Object val = null; - TableViewForm viewForm = ctx.getForm(); - - if (col != null) - { - if (null != viewForm && viewForm.contains(this, ctx)) - { - val = viewForm.get(getFormFieldName(ctx)); - } - else if (ctx.getRow() != null) - val = col.getValue(ctx); - } - - return val; - } - - @Override - public void addQueryFieldKeys(Set keys) - { - super.addQueryFieldKeys(keys); - keys.add(FieldKey.fromParts("Modified")); - if (_pkFieldKey != null) - keys.add(_pkFieldKey); - if (_objectURIFieldKey != null) - keys.add(_objectURIFieldKey); - } - - public static boolean filePathExist(String path, Container container, User user) - { - String davPath = path; - if (FileUtil.isUrlEncoded(davPath)) - davPath = FileUtil.decodeURL(davPath); - var resolver = WebdavService.get().getResolver(); - // Resolve path under webdav root - Path parsed = Path.parse(StringUtils.trim(davPath)); - - // Issue 52968: handle context path - Path contextPath = AppProps.getInstance().getParsedContextPath(); - if (parsed.startsWith(contextPath)) - parsed = parsed.subpath(contextPath.size(), parsed.size()); - - WebdavResource resource = resolver.lookup(parsed); - if ((null == resource || !resource.exists()) && !parsed.startsWith(new Path("_webdav"))) - resource = resolver.lookup(new Path("_webdav").append(parsed)); - if (resource != null && resource.isFile() && resource.canRead(user, true)) - { - return true; - } - else - { - // Resolve file under pipeline root - PipeRoot root = PipelineService.get().findPipelineRoot(container); - if (root != null) - { - // Attempt absolute path first, then relative path from pipeline root - File f = new File(path); - if (!root.isUnderRoot(f)) - f = root.resolvePath(path); - - return (NetworkDrive.exists(f) && root.isUnderRoot(f) && root.hasPermission(container, user, ReadPermission.class)); - } - } - - return false; - } - - @Override - protected String getFileName(RenderContext ctx, Object value) - { - return getFileName(ctx, value, false); - } - - @Override - protected String getFileName(RenderContext ctx, Object value, boolean isDisplay) - { - String result = value == null ? null : StringUtils.trimToNull(value.toString()); - if (result != null) - { - File f = null; - if (result.startsWith("file:")) - { - try - { - f = new File(new URI(result)); - } - catch (URISyntaxException x) - { - // try to recover - result = result.substring("file:".length()); - } - } - if (null == f) - f = FileUtil.getAbsoluteCaseSensitiveFile(new File(result)); - NetworkDrive.ensureDrive(f.getPath()); - List fileRootTypes = List.of(FileContentService.ContentType.files, FileContentService.ContentType.pipeline, FileContentService.ContentType.assayfiles); - boolean valid = false; - List containers = new ArrayList<>(); - containers.add(_container); - // Not ideal, but needed in case data is queried from cross folder context - if (ctx.get("folder") != null || ctx.get("container") != null) - { - Object folderObj = ctx.get("folder"); - if (folderObj == null) - folderObj = ctx.get("container"); - if (folderObj instanceof String containerId) - { - Container dataContainer = ContainerManager.getForId(containerId); - if (dataContainer != null && !dataContainer.equals(_container)) - containers.add(dataContainer); - } - } - for (Container container : containers) - { - if (valid) - break; - - for (FileContentService.ContentType fileRootType : fileRootTypes) - { - result = relativize(f, FileContentService.get().getFileRoot(container, fileRootType)); - if (result != null) - { - // Issue 54062: Strip folder name from displayed name - if (isDisplay) - result = f.getName(); - - valid = true; - break; - } - } - } - if (result == null) - { - result = f.getName(); - } - - if ((!valid || !f.exists()) && !result.endsWith(UNAVAILABLE_FILE_SUFFIX)) - result += UNAVAILABLE_FILE_SUFFIX; - } - return result; - } - - public static String relativize(File f, File fileRoot) - { - if (fileRoot != null) - { - NetworkDrive.ensureDrive(fileRoot.getPath()); - fileRoot = FileUtil.getAbsoluteCaseSensitiveFile(fileRoot); - if (URIUtil.isDescendant(fileRoot.toURI(), f.toURI())) - { - try - { - return FileUtil.relativize(fileRoot, f, false); - } - catch (IOException ignored) {} - } - } - return null; - } - - @Override - protected InputStream getFileContents(RenderContext ctx, Object ignore) throws FileNotFoundException - { - Object value = getValue(ctx); - String s = value == null ? null : StringUtils.trimToNull(value.toString()); - if (s != null) - { - File f = new File(s); - if (f.isFile()) - return new FileInputStream(f); - } - return null; - } - - @Override - protected void renderIconAndFilename( - RenderContext ctx, - HtmlWriter out, - String fileValue /*Could be raw path value, or processed filename by `getFileName`*/, - boolean link, - boolean thumbnail) - { - Object value = getValue(ctx); - String strValue = value == null ? null : StringUtils.trimToNull(value.toString()); - if (strValue != null && !fileValue.endsWith(UNAVAILABLE_FILE_SUFFIX)) - { - File f; - if (strValue.startsWith("file:")) - f = new File(URI.create(strValue)); - else - f = new File(strValue); - - if (!f.exists()) - { - // try all file root - List fileRootTypes = List.of(FileContentService.ContentType.files, FileContentService.ContentType.pipeline, FileContentService.ContentType.assayfiles); - for (FileContentService.ContentType fileRootType : fileRootTypes) - { - String fullPath = FileContentService.get().getFileRoot(_container, fileRootType).getAbsolutePath()+ File.separator + value; - f = new File(fullPath); - if (f.exists()) - break; - } - } - - // It's probably a file, so check that first - if (f.isFile()) - { - super.renderIconAndFilename(ctx, out, strValue, link, thumbnail); - } - else if (f.isDirectory()) - { - super.renderIconAndFilename(ctx, out, strValue, Attachment.getFileIcon(".folder"), null, link, false); - } - else - { - // It's not on the file system anymore, so don't offer a link and tell the user it's unavailable - super.renderIconAndFilename(ctx, out, strValue, Attachment.getFileIcon(fileValue), null, false, false); - } - } - else - { - super.renderIconAndFilename(ctx, out, fileValue, link, thumbnail); - } - } - - @Override - public Object getDisplayValue(RenderContext ctx) - { - return getFileName(ctx, super.getDisplayValue(ctx), true); - } - - @Override - public Object getJsonValue(RenderContext ctx) - { - return getFileName(ctx, super.getDisplayValue(ctx)); - } - - @Override - public Object getExportCompatibleValue(RenderContext ctx) - { - return getJsonValue(ctx); - } - - @Override - public boolean isFilterable() - { - return false; - } - @Override - public boolean isSortable() - { - return false; - } - -} +/* + * Copyright (c) 2008-2019 LabKey Corporation + * + * 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.labkey.api.study.assay; + +import org.apache.commons.lang3.StringUtils; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.labkey.api.admin.CoreUrls; +import org.labkey.api.attachments.Attachment; +import org.labkey.api.data.AbstractFileDisplayColumn; +import org.labkey.api.data.ColumnInfo; +import org.labkey.api.data.Container; +import org.labkey.api.data.ContainerManager; +import org.labkey.api.data.DisplayColumn; +import org.labkey.api.data.RemappingDisplayColumnFactory; +import org.labkey.api.data.RenderContext; +import org.labkey.api.data.TableViewForm; +import org.labkey.api.exp.PropertyDescriptor; +import org.labkey.api.files.FileContentService; +import org.labkey.api.pipeline.PipeRoot; +import org.labkey.api.pipeline.PipelineService; +import org.labkey.api.query.DetailsURL; +import org.labkey.api.query.FieldKey; +import org.labkey.api.query.SchemaKey; +import org.labkey.api.security.User; +import org.labkey.api.security.permissions.ReadPermission; +import org.labkey.api.settings.AppProps; +import org.labkey.api.util.ContainerContext; +import org.labkey.api.util.FileUtil; +import org.labkey.api.util.NetworkDrive; +import org.labkey.api.util.PageFlowUtil; +import org.labkey.api.util.Path; +import org.labkey.api.util.URIUtil; +import org.labkey.api.view.ActionURL; +import org.labkey.api.webdav.WebdavResource; +import org.labkey.api.webdav.WebdavService; +import org.labkey.api.writer.HtmlWriter; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class FileLinkDisplayColumn extends AbstractFileDisplayColumn +{ + // Issue 46282 - let admins choose if files should be rendered inside browser or downloaded as files + public static final String AS_ATTACHMENT_FORMAT = "attachment"; + public static final String AS_INLINE_FORMAT = "inline"; + + public static class Factory implements RemappingDisplayColumnFactory + { + private final Container _container; + + private PropertyDescriptor _pd; + private DetailsURL _detailsUrl; + private SchemaKey _schemaKey; + private String _queryName; + private FieldKey _pkFieldKey; + private FieldKey _objectURIFieldKey; + + public Factory(PropertyDescriptor pd, Container c, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull FieldKey pkFieldKey) + { + _pd = pd; + _container = c; + _schemaKey = schemaKey; + _queryName = queryName; + _pkFieldKey = pkFieldKey; + } + + public Factory(PropertyDescriptor pd, Container c, @NotNull FieldKey lsidColumnFieldKey) + { + _pd = pd; + _container = c; + _objectURIFieldKey = lsidColumnFieldKey; + } + + public Factory(DetailsURL detailsURL, Container c, @NotNull FieldKey pkFieldKey) + { + _detailsUrl = detailsURL; + _container = c; + _pkFieldKey = pkFieldKey; + } + + @Override + public Factory remapFieldKeys(@Nullable FieldKey parent, @Nullable Map remap) + { + Factory remapped = this.clone(); + if (remapped._pkFieldKey != null) + { + remapped._pkFieldKey = FieldKey.remap(_pkFieldKey, parent, remap); + if (null == remapped._pkFieldKey) + remapped._pkFieldKey = _pkFieldKey; + } + if (remapped._objectURIFieldKey != null) + { + remapped._objectURIFieldKey = FieldKey.remap(_objectURIFieldKey, parent, remap); + if (null == remapped._objectURIFieldKey) + remapped._objectURIFieldKey = _objectURIFieldKey; + } + return remapped; + } + + @Override + public DisplayColumn createRenderer(ColumnInfo col) + { + if (_pd == null && _detailsUrl != null) + return new FileLinkDisplayColumn(col, _detailsUrl, _container, _pkFieldKey); + else if (_pkFieldKey != null) + return new FileLinkDisplayColumn(col, _pd, _container, _schemaKey, _queryName, _pkFieldKey); + else if (_container != null) + return new FileLinkDisplayColumn(col, _pd, _container, _objectURIFieldKey); + else + throw new IllegalArgumentException("Cannot create a renderer from the specified configuration properties"); + } + + @Override + public FileLinkDisplayColumn.Factory clone() + { + try + { + return (Factory)super.clone(); + } + catch (CloneNotSupportedException e) + { + throw new RuntimeException(e); + } + } + } + + private final Container _container; + + private FieldKey _pkFieldKey; + private FieldKey _objectURIFieldKey; + + /** Use schemaName/queryName and pk FieldKey value to resolve File in CoreController.DownloadFileLinkAction. */ + public FileLinkDisplayColumn(ColumnInfo col, PropertyDescriptor pd, Container container, @NotNull SchemaKey schemaKey, @NotNull String queryName, @NotNull FieldKey pkFieldKey) + { + super(col); + _container = container; + _pkFieldKey = pkFieldKey; + + if (pd.getURL() == null) + { + // Don't stomp over an explicitly configured URL on this column + StringBuilder sb = new StringBuilder("/core/downloadFileLink.view?propertyId="); + sb.append(pd.getPropertyId()); + sb.append("&schemaName="); + sb.append(PageFlowUtil.encodeURIComponent(schemaKey.toString())); + sb.append("&queryName="); + sb.append(PageFlowUtil.encodeURIComponent(queryName)); + sb.append("&pk=${"); + sb.append(pkFieldKey); + sb.append("}"); + sb.append("&modified=${Modified}"); + if (AS_ATTACHMENT_FORMAT.equalsIgnoreCase(col.getFormat())) + { + sb.append("&inline=false"); + } + ContainerContext context = new ContainerContext.FieldKeyContext(new FieldKey(pkFieldKey.getParent(), "Folder")); + setURLExpression(DetailsURL.fromString(sb.toString(), context)); + } + } + + /** Use LSID FieldKey value as ObjectURI to resolve File in CoreController.DownloadFileLinkAction. */ + public FileLinkDisplayColumn(ColumnInfo col, PropertyDescriptor pd, Container container, @NotNull FieldKey objectURIFieldKey) + { + super(col); + _container = container; + _objectURIFieldKey = objectURIFieldKey; + + if (pd.getURL() == null) + { + // Don't stomp over an explicitly configured URL on this column + + ActionURL baseUrl = PageFlowUtil.urlProvider(CoreUrls.class).getDownloadFileLinkBaseURL(container, pd); + if (AS_ATTACHMENT_FORMAT.equalsIgnoreCase(col.getFormat())) + { + baseUrl.addParameter("inline", "false"); + } + else + { + setLinkTarget("_blank"); + } + DetailsURL url = new DetailsURL(baseUrl, "objectURI", objectURIFieldKey); + setURLExpression(url); + } + } + + public FileLinkDisplayColumn(ColumnInfo col, DetailsURL detailsURL, Container container, @NotNull FieldKey pkFieldKey) + { + super(col); + _container = container; + _pkFieldKey = pkFieldKey; + + setURLExpression(detailsURL); + } + + @Override + protected Object getInputValue(RenderContext ctx) + { + ColumnInfo col = getColumnInfo(); + Object val = null; + TableViewForm viewForm = ctx.getForm(); + + if (col != null) + { + if (null != viewForm && viewForm.contains(this, ctx)) + { + val = viewForm.get(getFormFieldName(ctx)); + } + else if (ctx.getRow() != null) + val = col.getValue(ctx); + } + + return val; + } + + @Override + public void addQueryFieldKeys(Set keys) + { + super.addQueryFieldKeys(keys); + keys.add(FieldKey.fromParts("Modified")); + if (_pkFieldKey != null) + keys.add(_pkFieldKey); + if (_objectURIFieldKey != null) + keys.add(_objectURIFieldKey); + } + + public static boolean filePathExist(String path, Container container, User user) + { + String davPath = path; + if (FileUtil.isUrlEncoded(davPath)) + davPath = FileUtil.decodeURL(davPath); + var resolver = WebdavService.get().getResolver(); + // Resolve path under webdav root + Path parsed = Path.parse(StringUtils.trim(davPath)); + + // Issue 52968: handle context path + Path contextPath = AppProps.getInstance().getParsedContextPath(); + if (parsed.startsWith(contextPath)) + parsed = parsed.subpath(contextPath.size(), parsed.size()); + + WebdavResource resource = resolver.lookup(parsed); + if ((null == resource || !resource.exists()) && !parsed.startsWith(new Path("_webdav"))) + resource = resolver.lookup(new Path("_webdav").append(parsed)); + if (resource != null && resource.isFile() && resource.canRead(user, true)) + { + return true; + } + else + { + // Resolve file under pipeline root + PipeRoot root = PipelineService.get().findPipelineRoot(container); + if (root != null) + { + // Attempt absolute path first, then relative path from pipeline root + File f = new File(path); + if (!root.isUnderRoot(f)) + f = root.resolvePath(path); + + return (NetworkDrive.exists(f) && root.isUnderRoot(f) && root.hasPermission(container, user, ReadPermission.class)); + } + } + + return false; + } + + @Override + protected String getFileName(RenderContext ctx, Object value) + { + return getFileName(ctx, value, false); + } + + @Override + protected String getFileName(RenderContext ctx, Object value, boolean isDisplay) + { + String result = value == null ? null : StringUtils.trimToNull(value.toString()); + if (result != null) + { + File f = null; + if (result.startsWith("file:")) + { + try + { + f = new File(new URI(result)); + } + catch (URISyntaxException x) + { + // try to recover + result = result.substring("file:".length()); + } + } + if (null == f) + f = FileUtil.getAbsoluteCaseSensitiveFile(new File(result)); + NetworkDrive.ensureDrive(f.getPath()); + List fileRootTypes = List.of(FileContentService.ContentType.files, FileContentService.ContentType.pipeline, FileContentService.ContentType.assayfiles); + boolean valid = false; + List containers = new ArrayList<>(); + containers.add(_container); + // Not ideal, but needed in case data is queried from cross folder context + if (ctx.get("folder") != null || ctx.get("container") != null) + { + Object folderObj = ctx.get("folder"); + if (folderObj == null) + folderObj = ctx.get("container"); + if (folderObj instanceof String containerId) + { + Container dataContainer = ContainerManager.getForId(containerId); + if (dataContainer != null && !dataContainer.equals(_container)) + containers.add(dataContainer); + } + } + for (Container container : containers) + { + if (valid) + break; + + for (FileContentService.ContentType fileRootType : fileRootTypes) + { + result = relativize(f, FileContentService.get().getFileRoot(container, fileRootType)); + if (result != null) + { + // Issue 54062: Strip folder name from displayed name + if (isDisplay) + result = f.getName(); + + valid = true; + break; + } + } + } + if (result == null) + { + result = f.getName(); + } + + if ((!valid || !f.exists()) && !result.endsWith(UNAVAILABLE_FILE_SUFFIX)) + result += UNAVAILABLE_FILE_SUFFIX; + } + return result; + } + + public static String relativize(File f, File fileRoot) + { + if (fileRoot != null) + { + NetworkDrive.ensureDrive(fileRoot.getPath()); + fileRoot = FileUtil.getAbsoluteCaseSensitiveFile(fileRoot); + if (URIUtil.isDescendant(fileRoot.toURI(), f.toURI())) + { + try + { + return FileUtil.relativize(fileRoot, f, false); + } + catch (IOException ignored) {} + } + } + return null; + } + + @Override + protected InputStream getFileContents(RenderContext ctx, Object ignore) throws FileNotFoundException + { + Object value = getValue(ctx); + String s = value == null ? null : StringUtils.trimToNull(value.toString()); + if (s != null) + { + File f = new File(s); + if (f.isFile()) + return new FileInputStream(f); + } + return null; + } + + @Override + protected void renderIconAndFilename( + RenderContext ctx, + HtmlWriter out, + String fileValue /*Could be raw path value, or processed filename by `getFileName`*/, + boolean link, + boolean thumbnail) + { + Object value = getValue(ctx); + String strValue = value == null ? null : StringUtils.trimToNull(value.toString()); + if (strValue != null && !fileValue.endsWith(UNAVAILABLE_FILE_SUFFIX)) + { + File f; + if (strValue.startsWith("file:")) + f = new File(URI.create(strValue)); + else + f = new File(strValue); + + if (!f.exists()) + { + // try all file root + List fileRootTypes = List.of(FileContentService.ContentType.files, FileContentService.ContentType.pipeline, FileContentService.ContentType.assayfiles); + for (FileContentService.ContentType fileRootType : fileRootTypes) + { + String fullPath = FileContentService.get().getFileRoot(_container, fileRootType).getAbsolutePath()+ File.separator + value; + f = new File(fullPath); + if (f.exists()) + break; + } + } + + // It's probably a file, so check that first + if (f.isFile()) + { + super.renderIconAndFilename(ctx, out, strValue, link, thumbnail); + } + else if (f.isDirectory()) + { + super.renderIconAndFilename(ctx, out, strValue, Attachment.getFileIcon(".folder"), null, link, false); + } + else + { + // It's not on the file system anymore, so don't offer a link and tell the user it's unavailable + super.renderIconAndFilename(ctx, out, strValue, Attachment.getFileIcon(fileValue), null, false, false); + } + } + else + { + super.renderIconAndFilename(ctx, out, fileValue, link, thumbnail); + } + } + + @Override + public Object getDisplayValue(RenderContext ctx) + { + return getFileName(ctx, super.getDisplayValue(ctx), true); + } + + @Override + public Object getJsonValue(RenderContext ctx) + { + return getFileName(ctx, super.getDisplayValue(ctx)); + } + + @Override + public Object getExportCompatibleValue(RenderContext ctx) + { + return getJsonValue(ctx); + } + + @Override + public boolean isFilterable() + { + return false; + } + @Override + public boolean isSortable() + { + return false; + } + +}