Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
fde4925
More file path improvements, mostly around pipeline jobs
labkey-jeckels Oct 18, 2025
92cb958
Distinguish between failures
labkey-jeckels Oct 19, 2025
6c4aacc
Test fixes,
labkey-jeckels Oct 19, 2025
4a95775
Test fixes, fix line endings
labkey-jeckels Oct 20, 2025
5c8aea2
Merge branch 'develop' into fb_pipelineFileLike
labkey-jeckels Oct 20, 2025
7943797
Test fixes
labkey-jeckels Oct 20, 2025
5c84742
More FileLike
labkey-jeckels Oct 21, 2025
1590fbc
Merge branch 'develop' into fb_pipelineFileLike
labkey-jeckels Oct 21, 2025
e873cdd
Merge branch 'develop' into fb_pipelineFileLike
labkey-jeckels Oct 21, 2025
0d1736f
Fix build
labkey-jeckels Oct 21, 2025
1bb78a4
Test fixes
labkey-jeckels Oct 21, 2025
10eb9b1
Get rid of new validation causing StackOverflowError
labkey-jeckels Oct 21, 2025
b3cd207
CloudStoreService.getFileLike()
labkey-matthewb Oct 21, 2025
0ccca75
zip to the .zip
labkey-matthewb Oct 21, 2025
9ddb5c1
PipeRootImpl.ensureSystemDirectory()
labkey-matthewb Oct 21, 2025
513396e
Try returning S3 FileLike from getRootFileLike(). Are we ready?
labkey-matthewb Oct 22, 2025
f9b0cd3
Merge branch 'develop' into fb_pipelineFileLike
labkey-jeckels Oct 22, 2025
a2b6552
Test fixes
labkey-jeckels Oct 22, 2025
7d84a25
Avoid File conversion
labkey-jeckels Oct 22, 2025
5eb6e29
Try to support OutputStream
labkey-jeckels Oct 22, 2025
0184353
Merge branch 'refs/heads/develop' into fb_pipelineFileLike
labkey-jeckels Oct 23, 2025
0c5ae92
Removed unused variables, improve test credential handling, refresh b…
labkey-jeckels Oct 23, 2025
2c07190
Serialize FileSystemBlobStore._FileLike
labkey-matthewb Oct 23, 2025
37a9ad6
Serialize FileSystemBlobStore._FileLike
labkey-matthewb Oct 23, 2025
d92e8e9
Merge remote-tracking branch 'origin/develop' into fb_pipelineFileLike
labkey-matthewb Oct 23, 2025
c1cfda5
Issue 54156: Assay XAR with transform script can't be edited if the t…
labkey-jeckels Oct 24, 2025
3a6eb4d
Fix exists() check
labkey-jeckels Oct 24, 2025
fe1eef5
Fix sequence setting on crazy key names
labkey-jeckels Oct 25, 2025
d76b2d5
Merge branch 'develop' into fb_pipelineFileLike
labkey-jeckels Oct 27, 2025
709f99b
Test coverage for 54156, test code cleanup
labkey-jeckels Oct 27, 2025
75b6feb
Merge branch 'develop' into fb_pipelineFileLike
labkey-jeckels Oct 28, 2025
9f200e2
build(DeserializationContext ctx)
labkey-matthewb Oct 28, 2025
616592c
Merge remote-tracking branch 'origin/fb_pipelineFileLike' into fb_pip…
labkey-jeckels Oct 28, 2025
5e38a4c
Fix line endings
labkey-jeckels Oct 28, 2025
aa10113
Minor cleanup
labkey-jeckels Oct 28, 2025
a6413dd
Merge branch 'develop' into fb_pipelineFileLike
labkey-jeckels Oct 31, 2025
7a70323
Delete dead code, avoid using reflection
labkey-jeckels Oct 31, 2025
97cf1c9
Merge branch 'develop' into fb_pipelineFileLike
labkey-jeckels Oct 31, 2025
ad0e21e
Troubleshoot
labkey-jeckels Nov 1, 2025
1fa36bf
Troubleshoot
labkey-jeckels Nov 1, 2025
b0431e5
Troubleshoot
labkey-jeckels Nov 1, 2025
b564a5f
Troubleshoot
labkey-jeckels Nov 2, 2025
42656ea
Troubleshoot
labkey-jeckels Nov 3, 2025
70fbf49
Troubleshoot
labkey-jeckels Nov 3, 2025
cb8e272
Nulls are great
labkey-jeckels Nov 3, 2025
5125134
Unix-style relative paths
labkey-jeckels Nov 4, 2025
fd76eeb
Dial back logging
labkey-jeckels Nov 4, 2025
b1823b8
Merge branch 'develop' into fb_pipelineFileLike
labkey-jeckels Nov 4, 2025
9b24eed
Refresh from develop
labkey-jeckels Nov 4, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 8 additions & 9 deletions api/src/org/labkey/api/admin/FolderImportContext.java
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,10 @@
import org.labkey.api.util.XmlValidationException;
import org.labkey.api.writer.VirtualFile;
import org.labkey.folder.xml.FolderDocument;
import org.labkey.vfs.FileLike;

import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
Expand All @@ -47,7 +46,7 @@
*/
public class FolderImportContext extends AbstractFolderContext
{
private Path _folderXml;
private FileLike _folderXml;

private String _xarJobId;

Expand All @@ -66,7 +65,7 @@ public FolderImportContext()
super(null, null, null, null, null, null);
}

public FolderImportContext(User user, Container c, Path folderXml, Set<String> dataTypes, LoggerGetter logger, VirtualFile root)
public FolderImportContext(User user, Container c, FileLike folderXml, Set<String> dataTypes, LoggerGetter logger, VirtualFile root)
{
super(user, c, null, dataTypes, logger, root);
_folderXml = folderXml;
Expand Down Expand Up @@ -112,17 +111,17 @@ public synchronized FolderDocument getDocument() throws ImportException
return folderDoc;
}

private FolderDocument readFolderDocument(Path folderXml) throws ImportException, IOException
private FolderDocument readFolderDocument(FileLike folderXml) throws ImportException, IOException
{
if (!Files.exists(folderXml))
throw new ImportException(folderXml.getFileName() + " file does not exist.");
if (!folderXml.exists())
throw new ImportException(folderXml.getName() + " file does not exist.");

FolderDocument folderDoc;

try (InputStream inputStream = Files.newInputStream(folderXml))
try (InputStream inputStream = folderXml.openInputStream())
{
folderDoc = FolderDocument.Factory.parse(inputStream, XmlBeansUtil.getDefaultParseOptions());
XmlBeansUtil.validateXmlDocument(folderDoc, folderXml.getFileName().toString());
XmlBeansUtil.validateXmlDocument(folderDoc, folderXml.getName());
}
catch (XmlException | XmlValidationException e)
{
Expand Down
7 changes: 4 additions & 3 deletions api/src/org/labkey/api/admin/ImportOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import org.labkey.api.data.Activity;
import org.labkey.api.security.User;
import org.labkey.api.security.UserManager;
import org.labkey.vfs.FileLike;

import java.nio.file.Path;
import java.util.Collection;
Expand All @@ -41,7 +42,7 @@ public class ImportOptions
private final Collection<String> _messages = new LinkedList<>();
private Set<String> _dataTypes;
private Activity _activity;
private Path _analysisDir;
private FileLike _analysisDir;
private String _folderArchiveSourceName = null;

private boolean _isNewFolderImport; // if we know the target folder is empty, can skip certain merge logic
Expand Down Expand Up @@ -142,12 +143,12 @@ public void setActivity(Activity activity)
_activity = activity;
}

public Path getAnalysisDir()
public FileLike getAnalysisDir()
{
return _analysisDir;
}

public void setAnalysisDir(Path analysisDir)
public void setAnalysisDir(FileLike analysisDir)
{
_analysisDir = analysisDir;
}
Expand Down
17 changes: 4 additions & 13 deletions api/src/org/labkey/api/admin/InvalidFileException.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,18 +20,19 @@
import org.apache.xmlbeans.XmlException;
import org.labkey.api.util.XmlValidationException;
import org.labkey.api.writer.VirtualFile;
import org.labkey.vfs.FileLike;

import java.io.File;
import java.nio.file.Path;

public class InvalidFileException extends ImportException
{
@Deprecated // prefer the Path version
public InvalidFileException(File root, File file, Throwable t)
public InvalidFileException(FileLike root, FileLike file, Throwable t)
{
this(root.toPath(), file.toPath(), t);
this(root.toNioPathForRead(), file.toNioPathForRead(), t);
}

@Deprecated // prefer the FileLike version
public InvalidFileException(Path root, Path file, Throwable t)
{
super(getErrorString(root, file, t.getMessage()));
Expand All @@ -42,21 +43,11 @@ public InvalidFileException(VirtualFile root, File file, Throwable t)
super(getErrorString(root.getRelativePath(file.getName()), t.getMessage()));
}

public InvalidFileException(File root, File file, XmlException e)
{
super(getErrorString(root, file, e));
}

public InvalidFileException(VirtualFile root, File file, XmlException e)
{
super(getErrorString(root, file, e));
}

public InvalidFileException(File root, File file, XmlValidationException e)
{
super(getErrorString(root, file, (String)null), e);
}

public InvalidFileException(VirtualFile root, File file, XmlValidationException e)
{
super(getErrorString(root.getRelativePath(file.getName()), null), e);
Expand Down
39 changes: 10 additions & 29 deletions api/src/org/labkey/api/assay/AbstractAssayProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,6 @@
import org.labkey.api.view.NotFoundException;
import org.labkey.api.view.ViewContext;
import org.labkey.vfs.FileLike;
import org.labkey.vfs.FileSystemLike;
import org.springframework.validation.BindException;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;
Expand Down Expand Up @@ -285,12 +284,7 @@ public ActionURL linkToStudy(User user, Container assayDataContainer, ExpProtoco
dataMap.put(StudyPublishService.TARGET_STUDY_PROPERTY_NAME, targetStudyContainer);

// Remember which rows we're planning to link, partitioned by the target study
Set<Long> rowIds = rowIdsByTargetContainer.get(targetStudyContainer);
if (rowIds == null)
{
rowIds = new HashSet<>();
rowIdsByTargetContainer.put(targetStudyContainer, rowIds);
}
Set<Long> rowIds = rowIdsByTargetContainer.computeIfAbsent(targetStudyContainer, k -> new HashSet<>());
rowIds.add(publishKey.getDataId());

dataMaps.add(dataMap);
Expand Down Expand Up @@ -732,7 +726,7 @@ private void addReusableData(Map<String, org.labkey.vfs.FileLike> reusableFiles,
}

@Override
public AssayRunCreator getRunCreator()
public AssayRunCreator<?> getRunCreator()
{
return new DefaultAssayRunCreator<>(this);
}
Expand Down Expand Up @@ -1459,13 +1453,13 @@ public AssaySaveHandler getSaveHandler()
}

@Override
public AssayRunUploadContext.Factory<? extends AbstractAssayProvider, ? extends AssayRunUploadContext.Factory> createRunUploadFactory(ExpProtocol protocol, ViewContext context)
public AssayRunUploadContext.Factory<? extends AbstractAssayProvider, ? extends AssayRunUploadContext.Factory<?, ?>> createRunUploadFactory(ExpProtocol protocol, ViewContext context)
{
return new AssayRunUploadContextImpl.Factory<>(protocol, this, context);
}

@Override
public AssayRunUploadContext.Factory<? extends AbstractAssayProvider, ? extends AssayRunUploadContext.Factory> createRunUploadFactory(ExpProtocol protocol, User user, Container c)
public AssayRunUploadContext.Factory<? extends AbstractAssayProvider, ? extends AssayRunUploadContext.Factory<?, ?>> createRunUploadFactory(ExpProtocol protocol, User user, Container c)
{
return new AssayRunUploadContextImpl.Factory<>(protocol, this, user, c);
}
Expand Down Expand Up @@ -1518,15 +1512,15 @@ public DataExchangeHandler createDataExchangeHandler()
}

@Override
public AssayRunDatabaseContext createRunDatabaseContext(ExpRun run, User user, HttpServletRequest request)
public AssayRunDatabaseContext<?> createRunDatabaseContext(ExpRun run, User user, HttpServletRequest request)
{
return new AssayRunDatabaseContext(run, user, request);
return new AssayRunDatabaseContext<>(run, user, request);
}

@Override
public AssayRunAsyncContext<?> createRunAsyncContext(AssayRunUploadContext<?> context) throws IOException, ExperimentException
{
return new AssayRunAsyncContext(context);
return new AssayRunAsyncContext<>(context);
}

@Override
Expand Down Expand Up @@ -1698,16 +1692,6 @@ public Pair<ExpProtocol, Integer> getAssayResultRowIdFromLsid(Container containe
return Pair.of(protocol, rowId);
}

@Override
public @Nullable ActionURL getResultRowURL(Container container, Lsid lsid)
{
var pair = getAssayResultRowIdFromLsid(container, lsid);
if (pair == null)
return null;

return PageFlowUtil.urlProvider(AssayUrls.class).getAssayResultRowURL(this, container, pair.first, pair.second);
}

@Override
public boolean supportsFlagColumnType(ExpProtocol.AssayDomainTypes type)
{
Expand Down Expand Up @@ -2141,18 +2125,15 @@ private void updateDataFileUrl(List<ExpRun> runs, Container sourceContainer, Con
AuditLogService.get().addEvent(user, event);
}
}
catch (Exception e)
{

}
catch (Exception ignored) {}
}
}

protected void moveAssayResults(List<ExpRun> runs, ExpProtocol protocol, Container sourceContainer, Container targetContainer, User user, AssayMoveData assayMoveData) throws ExperimentException
{
String tableName = AssayProtocolSchema.DATA_TABLE_NAME;
AssaySchema schema = createProtocolSchema(user, targetContainer, protocol, null);
FilteredTable assayResultTable = (FilteredTable) schema.getTable(tableName);
FilteredTable<?> assayResultTable = (FilteredTable<?>) schema.getTable(tableName);
if (assayResultTable == null)
return;

Expand All @@ -2161,7 +2142,7 @@ protected void moveAssayResults(List<ExpRun> runs, ExpProtocol protocol, Contain

record AssayFileMoveReference(String sourceFilePath, File updatedFile, String runName, String fieldName) {}

private void updateResultFiles(FilteredTable assayResultTable, List<ExpRun> runs, ExpProtocol assayProtocol, Container sourceContainer, Container targetContainer, User user, AssayMoveData assayMoveData) throws ExperimentException
private void updateResultFiles(FilteredTable<?> assayResultTable, List<ExpRun> runs, ExpProtocol assayProtocol, Container sourceContainer, Container targetContainer, User user, AssayMoveData assayMoveData) throws ExperimentException
{
FileContentService fileContentService = FileContentService.get();
if (fileContentService == null)
Expand Down
7 changes: 1 addition & 6 deletions api/src/org/labkey/api/assay/AssayProvider.java
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,7 @@ enum Scope
*/
DataExchangeHandler createDataExchangeHandler();
/** Make a context that knows how to update a run that's already been stored in the database */
AssayRunDatabaseContext createRunDatabaseContext(ExpRun run, User user, HttpServletRequest request);
AssayRunDatabaseContext<?> createRunDatabaseContext(ExpRun run, User user, HttpServletRequest request);
/**
* Make a context that knows how to do the import in the background, on a separate thread
* (and therefore detached from the HTTP request that might have spawned it)
Expand All @@ -322,11 +322,6 @@ enum Scope
@Nullable
Pair<ExpProtocol, Integer> getAssayResultRowIdFromLsid(Container container, Lsid assayResultRowLsid);

/**
* Get the URL for an assay result row's LSID.
*/
@Nullable ActionURL getResultRowURL(Container container, Lsid lsid);

/**
* Return a SQL pattern that can be used to match a protocol's LSID to this AssayProvider.
* The pattern must match a protocol's LSID in the same manner as
Expand Down
2 changes: 2 additions & 0 deletions api/src/org/labkey/api/assay/AssayService.java
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,8 @@ ParticipantVisitResolver createResolver(User user, ExpRun run, @Nullable ExpProt
/** Returns the lineage "role" for an assay run/result property. */
@NotNull String getPropertyInputLineageRole(@NotNull DomainProperty dp);

AssayDomainService createAssayDomainService(User user, Container container);

interface ResultsCheckHelper
{
@NotNull Logger getLogger();
Expand Down
4 changes: 2 additions & 2 deletions api/src/org/labkey/api/assay/DefaultAssayRunCreator.java
Original file line number Diff line number Diff line change
Expand Up @@ -221,9 +221,9 @@ private ExpExperiment saveExperimentRunAsync(AssayRunUploadContext<ProviderType>
// Choose another file as the primary
primaryFile = context.getUploadedData().entrySet().iterator().next().getValue();
}
primaryFile = Objects.requireNonNull(primaryFile);
Objects.requireNonNull(primaryFile);
AssayRunAsyncContext asyncContext = context.getProvider().createRunAsyncContext(context);
final AssayUploadPipelineJob<ProviderType> pipelineJob = new AssayUploadPipelineJob<ProviderType>(
final AssayUploadPipelineJob<ProviderType> pipelineJob = new AssayUploadPipelineJob<>(
asyncContext,
info,
batch,
Expand Down
8 changes: 4 additions & 4 deletions api/src/org/labkey/api/assay/transform/AnalysisScript.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,13 @@ private AnalysisScript(File script, List<String> operations)

private AnalysisScript(File script)
{
try
if (!script.exists())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like there is a direct path from SaveProtocolAction to here, however it's not clear that any FS specific permissions have been validated. Should there be a version of FileSystemLike.wrap (or this new?) method take a user or proof of authorization for user events? (we could also have an .unsafe() to mimic usages elsewhere e.g. SQLFragment() and HtmlString()).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the check here:

if (!submittedScripts.isEmpty() && !canUpdateTransformationScript())

It would be nice to switch to mandating that all scripts be placed in the special WebDav @scripts directory or in a module, at which point AnalysisScript could use that as the root.

I don't think that we can put a permission check at this level as the user who is executing the script by uploading assay data may not have the permissions to configure the script in the first place.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{
_script = FileSystemLike.wrapFile(script.getParentFile(), script);
_script = new FileSystemLike.Builder(script).build().getRoot();
}
catch (IOException e)
else
{
throw UnexpectedException.wrap(e);
_script = FileSystemLike.wrapFile(script);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public interface CloudArchiveImporterSupport
default void downloadCloudArchive(@NotNull PipelineJob job, @NotNull Path studyXml, BindException errors) throws UnsupportedOperationException
{
//check if cloud based pipeline root, and study xml hasn't been downloaded already
if (!studyXml.startsWith(job.getPipeRoot().getImportDirectory().toPath().toAbsolutePath()))
if (!studyXml.startsWith(job.getPipeRoot().getImportDirectory().toNioPathForRead().toAbsolutePath()))
{
if (CloudStoreService.get() != null) //proxy of is Cloud Module enabled for the current job/container
{
Expand Down
13 changes: 13 additions & 0 deletions api/src/org/labkey/api/cloud/CloudStoreService.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import org.labkey.api.services.ServiceRegistry;
import org.labkey.api.util.Pair;
import org.labkey.api.webdav.WebdavResource;
import org.labkey.vfs.FileLike;
import org.labkey.vfs.FileSystemLike;

import java.nio.file.Path;
import java.util.Collection;
Expand Down Expand Up @@ -141,6 +143,17 @@ default Collection<String> getEnabledCloudStores(Container container, boolean ex
@Nullable
Path getPath(Container container, String storeName, org.labkey.api.util.Path path);

/**
* Return nio.Path to cloud file/directory
*/
@Nullable
FileLike getFileLike(Container container, String storeName, org.labkey.api.util.Path path);

/**
* Return FileSystem for cloud root
*/
public @Nullable FileSystemLike getFileSystemLike(Container container, String configName);

/**
* Return path relative to cloud store
*/
Expand Down
15 changes: 7 additions & 8 deletions api/src/org/labkey/api/data/TSVGridWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,9 @@
import org.jetbrains.annotations.Nullable;
import org.labkey.api.collections.ResultSetRowMapFactory;
import org.labkey.api.query.FieldKey;
import org.labkey.api.util.FileUtil;
import org.labkey.api.view.HttpView;
import org.labkey.vfs.FileLike;

import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
Expand Down Expand Up @@ -198,7 +197,7 @@ private int writeBody(Results results)
* @return List of the output Files.
*/
@NotNull
public List<File> writeBatchFiles(@NotNull File outputDir, @NotNull String baseName, @Nullable String extension, int batchSize, @Nullable FieldKey batchColumn)
public List<FileLike> writeBatchFiles(@NotNull FileLike outputDir, @NotNull String baseName, @Nullable String extension, int batchSize, @Nullable FieldKey batchColumn)
{
extension = StringUtils.trimToEmpty(extension);
String ext = "".equals(extension) || extension.startsWith(".") ? extension : "." + extension;
Expand All @@ -211,13 +210,13 @@ public List<File> writeBatchFiles(@NotNull File outputDir, @NotNull String baseN
}

@NotNull
private List<File> writeResultSetBatches(Results results, File outputDir, String baseName, String extension, int batchSize, @Nullable FieldKey batchColumn) throws IOException
private List<FileLike> writeResultSetBatches(Results results, FileLike outputDir, String baseName, String extension, int batchSize, @Nullable FieldKey batchColumn) throws IOException
{
int currentBatchSize = 0;
int totalBatches = 1;
Object previousBatchColumnValue = null;
Object newBatchColumnValue;
List<File> outputFiles = new ArrayList<>();
List<FileLike> outputFiles = new ArrayList<>();
outputFiles.add(startBatchFile(outputDir, baseName, extension, batchSize, totalBatches));
RenderContext ctx = getRenderContext();
ctx.setResults(results);
Expand Down Expand Up @@ -264,11 +263,11 @@ private List<File> writeResultSetBatches(Results results, File outputDir, String
}

@NotNull
private File startBatchFile(File outputDir, String baseName, String extension, int batchSize, int totalBatches) throws IOException
private FileLike startBatchFile(FileLike outputDir, String baseName, String extension, int batchSize, int totalBatches) throws IOException
{
String batchId = batchSize == 0 ? "" : "-" + totalBatches;
File file = FileUtil.appendName(outputDir, baseName + batchId + extension);
prepare(file);
FileLike file = outputDir.resolveChild(baseName + batchId + extension);
prepare(file.openOutputStream());
writeFileHeader();
if (isHeaderRowVisible())
writeColumnHeaders();
Expand Down
Loading