diff --git a/legend-sdlc-server-fs/Dockerfile b/legend-sdlc-server-fs/Dockerfile
new file mode 100644
index 0000000000..0a6e98e8c1
--- /dev/null
+++ b/legend-sdlc-server-fs/Dockerfile
@@ -0,0 +1,9 @@
+FROM eclipse-temurin:11.0.17_8-jdk-jammy
+COPY target/legend-sdlc-server-fs-*-shaded.jar /app/bin/
+COPY src/main/resources/docker/config /config
+CMD java -cp /app/bin/*.jar \
+-XX:+ExitOnOutOfMemoryError \
+-XX:MaxRAMPercentage=60 \
+-Xss4M \
+-Dfile.encoding=UTF8 \
+org.finos.legend.sdlc.server.startup.LegendSDLCServerFS server /config/config.json
\ No newline at end of file
diff --git a/legend-sdlc-server-fs/pom.xml b/legend-sdlc-server-fs/pom.xml
new file mode 100644
index 0000000000..c24b4d547a
--- /dev/null
+++ b/legend-sdlc-server-fs/pom.xml
@@ -0,0 +1,272 @@
+
+
+
+
+
+ org.finos.legend.sdlc
+ legend-sdlc
+ 0.140.1-SNAPSHOT
+
+ 4.0.0
+
+ legend-sdlc-server-fs
+ Legend SDLC Server - FS
+ jar
+
+
+
+
+ org.apache.maven.plugins
+ maven-enforcer-plugin
+
+
+
+
+ org.slf4j:jcl-over-slf4j:${slf4j.version}
+ org.slf4j:jul-to-slf4j:${slf4j.version}
+ ch.qos.logback:logback-access
+ ch.qos.logback:logback-classic
+ ch.qos.logback:logback-core
+
+
+
+
+
+
+ maven-shade-plugin
+
+
+ package
+
+ shade
+
+
+ false
+
+
+ *:*
+
+ META-INF/*.SF
+ META-INF/*.DSA
+ META-INF/*.RSA
+
+
+
+ true
+
+
+
+ org.finos.legend.sdlc.server.startup.LegendSDLCServerFS
+
+
+
+
+
+
+
+
+
+
+
+
+ org.finos.legend.sdlc
+ legend-sdlc-model
+
+
+ org.finos.legend.sdlc
+ legend-sdlc-server-shared
+
+
+ org.glassfish.hk2.external
+ *
+
+
+
+
+ org.finos.legend.sdlc
+ legend-sdlc-extensions-collection-entity-serializer
+ runtime
+
+
+ org.finos.legend.sdlc
+ legend-sdlc-server
+
+
+ jakarta.servlet
+ *
+
+
+ jakarta.ws.rs
+ *
+
+
+ org.glassfish.hk2.external
+ *
+
+
+
+
+
+
+
+ org.finos.legend.engine
+ legend-engine-protocol-pure
+
+
+ org.finos.legend.engine
+ legend-engine-extensions-collection-generation
+ runtime
+
+
+
+
+
+ org.eclipse.collections
+ eclipse-collections-api
+
+
+ org.eclipse.collections
+ eclipse-collections
+
+
+ org.eclipse.jgit
+ org.eclipse.jgit
+ 6.3.0.202209071007-r
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-annotations
+
+
+
+ org.apache.httpcomponents
+ httpclient
+
+
+
+ com.google.inject
+ guice
+
+
+
+ io.dropwizard
+ dropwizard-core
+
+
+ io.dropwizard
+ dropwizard-lifecycle
+
+
+
+ com.hubspot.dropwizard
+ dropwizard-guicier
+
+
+
+ io.swagger
+ swagger-annotations
+
+
+
+ javax.inject
+ javax.inject
+
+
+
+ javax.servlet
+ javax.servlet-api
+
+
+ javax.ws.rs
+ javax.ws.rs-api
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+ junit
+ junit
+ test
+
+
+
+
+
+ docker
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+ false
+
+
+ deploy
+
+ build
+ tag
+ push
+
+
+
+
+ ${project.version}
+ ${env.DOCKER_USERNAME}
+ ${env.DOCKER_PASSWORD}
+ registry.hub.docker.com/${env.DOCKER_USERNAME}/${project.artifactId}
+
+
+
+
+
+
+
+ docker-snapshot
+
+
+
+ com.spotify
+ dockerfile-maven-plugin
+ false
+
+
+ deploy
+
+ build
+ tag
+ push
+
+
+
+
+ snapshot
+ ${env.DOCKER_USERNAME}
+ ${env.DOCKER_PASSWORD}
+ registry.hub.docker.com/${env.DOCKER_USERNAME}/${project.artifactId}
+
+
+
+
+
+
+
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/BaseFSApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/BaseFSApi.java
new file mode 100644
index 0000000000..6b74ad5a29
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/BaseFSApi.java
@@ -0,0 +1,145 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.finos.legend.sdlc.domain.model.revision.Revision;
+import org.finos.legend.sdlc.domain.model.revision.RevisionAlias;
+import org.finos.legend.sdlc.server.api.workspace.FileSystemWorkspaceApi;
+import org.finos.legend.sdlc.server.domain.api.project.source.ProjectSourceSpecification;
+import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification;
+import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecificationVisitor;
+import org.finos.legend.sdlc.server.domain.api.project.source.WorkspaceSourceSpecification;
+import org.finos.legend.sdlc.server.exception.FSException;
+import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider;
+import org.finos.legend.sdlc.server.startup.FSConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.File;
+
+public class BaseFSApi
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(BaseFSApi.class);
+ private final String rootDirectory;
+
+ protected BaseFSApi(FSConfiguration fsConfiguration)
+ {
+ this.rootDirectory = fsConfiguration.getRootDirectory();
+ }
+
+ protected String getRootDirectory()
+ {
+ return this.rootDirectory;
+ }
+
+ public Repository retrieveRepo(String projectId)
+ {
+ try
+ {
+ String repoDirPath = rootDirectory + File.separator + projectId + File.separator + ".git";
+ File repoDir = new File(repoDirPath);
+ if (repoDir.exists() && repoDir.isDirectory())
+ {
+ return FileRepositoryBuilder.create(repoDir);
+ }
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Repository " + projectId + " can't be found ", e);
+ }
+ return null;
+ }
+
+ public Ref getGitBranch(String projectId, String branchName)
+ {
+ String refBranchName = branchName;
+ try
+ {
+ Repository repository = retrieveRepo(projectId);
+ return repository.getRefDatabase().findRef(refBranchName);
+ }
+ catch (Exception e)
+ {
+ LOGGER.error("Error occurred during branch list operation for workspace {} in project {}", refBranchName, projectId, e);
+ throw FSException.getLegendSDLCServerException("Branch " + projectId + " does not exist", e);
+ }
+ }
+
+ public String getRefBranchName(SourceSpecification sourceSpecification)
+ {
+ return sourceSpecification.visit(new SourceSpecificationVisitor()
+ {
+ public String visit(ProjectSourceSpecification sourceSpec)
+ {
+ return "master";
+ }
+
+ public String visit(WorkspaceSourceSpecification workspaceSourceSpecification)
+ {
+ return FileSystemWorkspaceApi.getWorkspaceBranchName(workspaceSourceSpecification.getWorkspaceSpecification());
+ }
+ });
+ }
+
+ protected String resolveRevisionId(String revisionId, ProjectFileAccessProvider.RevisionAccessContext revisionAccessContext)
+ {
+ if (revisionId == null)
+ {
+ throw new IllegalArgumentException("Resolving revision alias does not work with null revisionId, null handling must be done before using this method");
+ }
+ RevisionAlias revisionAlias = getRevisionAlias(revisionId);
+ switch (revisionAlias)
+ {
+ case BASE:
+ {
+ Revision revision = revisionAccessContext.getBaseRevision();
+ return (revision == null) ? null : revision.getId();
+ }
+ case HEAD:
+ {
+ Revision revision = revisionAccessContext.getCurrentRevision();
+ return (revision == null) ? null : revision.getId();
+ }
+ case REVISION_ID:
+ {
+ return revisionId;
+ }
+ default:
+ {
+ throw new IllegalArgumentException("Unknown revision alias type: " + revisionAlias);
+ }
+ }
+ }
+
+ protected RevisionAlias getRevisionAlias(String revisionId)
+ {
+ if (revisionId == null)
+ {
+ return null;
+ }
+ if (RevisionAlias.BASE.getValue().equalsIgnoreCase(revisionId))
+ {
+ return RevisionAlias.BASE;
+ }
+ if (RevisionAlias.HEAD.getValue().equalsIgnoreCase(revisionId) || RevisionAlias.CURRENT.getValue().equalsIgnoreCase(revisionId) || RevisionAlias.LATEST.getValue().equalsIgnoreCase(revisionId))
+ {
+ return RevisionAlias.HEAD;
+ }
+ return RevisionAlias.REVISION_ID;
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/backup/FileSystemBackupApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/backup/FileSystemBackupApi.java
new file mode 100644
index 0000000000..808ceabe73
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/backup/FileSystemBackupApi.java
@@ -0,0 +1,41 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.backup;
+
+import org.finos.legend.sdlc.server.domain.api.backup.BackupApi;
+import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSpecification;
+import org.finos.legend.sdlc.server.exception.FSException;
+
+import javax.inject.Inject;
+
+public class FileSystemBackupApi implements BackupApi
+{
+ @Inject
+ public FileSystemBackupApi()
+ {
+ }
+
+ @Override
+ public void discardBackupWorkspace(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ FSException.unavailableFeature();
+ }
+
+ @Override
+ public void recoverBackupWorkspace(String projectId, WorkspaceSpecification workspaceSpecification, boolean forceRecovery)
+ {
+ FSException.unavailableFeature();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/build/FileSystemBuildApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/build/FileSystemBuildApi.java
new file mode 100644
index 0000000000..2e7d029a35
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/build/FileSystemBuildApi.java
@@ -0,0 +1,50 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.build;
+
+import org.finos.legend.sdlc.domain.model.project.workspace.WorkspaceType;
+import org.finos.legend.sdlc.domain.model.version.VersionId;
+import org.finos.legend.sdlc.server.exception.FSException;
+import org.finos.legend.sdlc.server.domain.api.build.BuildAccessContext;
+import org.finos.legend.sdlc.server.domain.api.build.BuildApi;
+import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider;
+
+import javax.inject.Inject;
+
+public class FileSystemBuildApi implements BuildApi
+{
+ @Inject
+ public FileSystemBuildApi()
+ {
+ }
+
+ @Override
+ public BuildAccessContext getProjectBuildAccessContext(String projectId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public BuildAccessContext getWorkspaceBuildAccessContext(String projectId, String workspaceId, WorkspaceType workspaceType, ProjectFileAccessProvider.WorkspaceAccessType workspaceAccessType)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public BuildAccessContext getVersionBuildAccessContext(String projectId, VersionId versionId)
+ {
+ throw FSException.unavailableFeature();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/comparison/FileSystemComparisonApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/comparison/FileSystemComparisonApi.java
new file mode 100644
index 0000000000..1611896666
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/comparison/FileSystemComparisonApi.java
@@ -0,0 +1,54 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.comparison;
+
+import org.finos.legend.sdlc.domain.model.comparison.Comparison;
+import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSpecification;
+import org.finos.legend.sdlc.server.exception.FSException;
+import org.finos.legend.sdlc.server.domain.api.comparison.ComparisonApi;
+
+import javax.inject.Inject;
+
+public class FileSystemComparisonApi implements ComparisonApi
+{
+ @Inject
+ public FileSystemComparisonApi()
+ {
+ }
+
+ @Override
+ public Comparison getWorkspaceCreationComparison(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Comparison getWorkspaceSourceComparison(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Comparison getReviewComparison(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Comparison getReviewWorkspaceCreationComparison(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+}
\ No newline at end of file
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/conflictresolution/FileSystemConflictResolutionApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/conflictresolution/FileSystemConflictResolutionApi.java
new file mode 100644
index 0000000000..ed1fcba732
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/conflictresolution/FileSystemConflictResolutionApi.java
@@ -0,0 +1,48 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.conflictresolution;
+
+import org.finos.legend.sdlc.server.application.entity.PerformChangesCommand;
+import org.finos.legend.sdlc.server.domain.api.conflictResolution.ConflictResolutionApi;
+import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSpecification;
+import org.finos.legend.sdlc.server.exception.FSException;
+
+import javax.inject.Inject;
+
+public class FileSystemConflictResolutionApi implements ConflictResolutionApi
+{
+ @Inject
+ public FileSystemConflictResolutionApi()
+ {
+ }
+
+ @Override
+ public void discardConflictResolution(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public void discardChangesConflictResolution(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public void acceptConflictResolution(String projectId, WorkspaceSpecification workspaceSpecification, PerformChangesCommand command)
+ {
+ throw FSException.unavailableFeature();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemApiWithFileAccess.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemApiWithFileAccess.java
new file mode 100644
index 0000000000..f683154015
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemApiWithFileAccess.java
@@ -0,0 +1,365 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.entity;
+
+import org.eclipse.collections.api.list.MutableList;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.lib.*;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.finos.legend.sdlc.domain.model.revision.Revision;
+import org.finos.legend.sdlc.server.api.BaseFSApi;
+import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification;
+import org.finos.legend.sdlc.server.domain.model.revision.FileSystemRevision;
+import org.finos.legend.sdlc.server.error.LegendSDLCServerException;
+import org.finos.legend.sdlc.server.exception.FSException;
+import org.finos.legend.sdlc.server.project.AbstractFileAccessContext;
+import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider;
+import org.finos.legend.sdlc.server.project.ProjectFileOperation;
+import org.finos.legend.sdlc.server.project.ProjectFiles;
+import org.finos.legend.sdlc.server.startup.FSConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.ws.rs.core.Response;
+import java.io.File;
+import java.nio.file.Files;
+import java.nio.file.StandardOpenOption;
+import java.time.Instant;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.function.Predicate;
+import java.util.stream.Stream;
+
+public abstract class FileSystemApiWithFileAccess extends BaseFSApi
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemApiWithFileAccess.class);
+
+ protected FileSystemApiWithFileAccess(FSConfiguration fsConfiguration)
+ {
+ super(fsConfiguration);
+ }
+
+ private class FileSystemFileAccessContext extends AbstractFileAccessContext
+ {
+ protected final String projectId;
+ private final SourceSpecification sourceSpecification;
+ private final String revisionId;
+
+ public FileSystemFileAccessContext(String projectId, SourceSpecification sourceSpecification, String revisionId)
+ {
+ this.projectId = projectId;
+ this.sourceSpecification = sourceSpecification;
+ this.revisionId = revisionId;
+ }
+
+ @Override
+ protected Stream getFilesInCanonicalDirectories(MutableList directories)
+ {
+ List files = new ArrayList<>();
+ Repository repo = retrieveRepo(this.projectId);
+ try
+ {
+ ObjectId commitId = ObjectId.fromString(revisionId);
+ RevCommit commit = repo.parseCommit(commitId);
+ RevTree tree = commit.getTree();
+ TreeWalk treeWalk = new TreeWalk(repo);
+ treeWalk.addTree(tree);
+ treeWalk.setRecursive(true);
+
+ while (treeWalk.next())
+ {
+ String path = treeWalk.getPathString();
+ if (directories.anySatisfy(d -> path.startsWith(d)))
+ {
+ files.add(getFile(path));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Error getting files in directories for " + projectId, e);
+ }
+ return files.stream();
+ }
+
+ @Override
+ public ProjectFileAccessProvider.ProjectFile getFile(String path)
+ {
+ String branchName = getRefBranchName(sourceSpecification);
+ try
+ {
+ Repository repo = retrieveRepo(this.projectId);
+ RevWalk revWalk = new RevWalk(repo);
+ RevCommit branchCommit = revWalk.parseCommit(repo.resolve(branchName));
+ RevTree branchTree = branchCommit.getTree();
+ path = path.startsWith("/") ? path.substring(1) : path;
+ try (TreeWalk treeWalk = TreeWalk.forPath(repo, path, branchTree))
+ {
+ if (treeWalk != null)
+ {
+ ObjectId objectId = treeWalk.getObjectId(0);
+ ObjectReader objectReader = repo.newObjectReader();
+ byte[] fileBytes = objectReader.open(objectId).getBytes();
+ return ProjectFiles.newByteArrayProjectFile(path, fileBytes);
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Error getting file " + path, e);
+ }
+ return null;
+ }
+
+ @Override
+ public boolean fileExists(String path)
+ {
+ String workspaceId = getRefBranchName(sourceSpecification);
+ try
+ {
+ Repository repo = retrieveRepo(this.projectId);
+ RevWalk revWalk = new RevWalk(repo);
+ RevCommit branchCommit = revWalk.parseCommit(repo.resolve(workspaceId));
+ RevTree branchTree = branchCommit.getTree();
+ path = path.startsWith("/") ? path.substring(1) : path;
+ try (TreeWalk treeWalk = TreeWalk.forPath(repo, path, branchTree))
+ {
+ return treeWalk != null;
+ }
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Error occurred while parsing Git commit for workspace " + workspaceId, e);
+ }
+ }
+ }
+
+ private class FileSystemRevisionAccessContext implements ProjectFileAccessProvider.RevisionAccessContext
+ {
+ private final String projectId;
+ private final SourceSpecification sourceSpecification;
+
+ public FileSystemRevisionAccessContext(String projectId, SourceSpecification sourceSpecification)
+ {
+ this.projectId = projectId;
+ this.sourceSpecification = Objects.requireNonNull(sourceSpecification, "source specification may not be null");
+ }
+
+ @Override
+ public Revision getCurrentRevision()
+ {
+ String branchName = getRefBranchName(sourceSpecification);
+ Repository repo = retrieveRepo(this.projectId);
+ try
+ {
+ ObjectId commitId = repo.resolve(branchName);
+ RevWalk revWalk = new RevWalk(repo);
+ RevCommit commit = revWalk.parseCommit(commitId);
+ revWalk.dispose();
+ return getRevisionInfo(commit);
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Failed to get current revision for branch " + branchName + " in project " + this.projectId, e);
+ }
+ }
+
+ @Override
+ public Revision getBaseRevision()
+ {
+ String branchName = getRefBranchName(sourceSpecification);
+ Repository repo = retrieveRepo(this.projectId);
+ try
+ {
+ Ref branchRef = repo.exactRef(Constants.R_HEADS + branchName);
+ ObjectId branchCommitId = branchRef.getObjectId();
+ ObjectId masterCommitId = repo.resolve(Constants.R_HEADS + "master");
+ RevWalk revWalk = new RevWalk(repo);
+ RevCommit branchCommit = revWalk.parseCommit(branchCommitId);
+ if (masterCommitId.equals(branchCommitId)) // If branch is master, return the first commit
+ {
+ RevCommit baseCommit = revWalk.parseCommit(masterCommitId);
+ revWalk.dispose();
+ return getRevisionInfo(baseCommit);
+ }
+ else
+ {
+ revWalk.markStart(branchCommit);
+ revWalk.markStart(revWalk.parseCommit(masterCommitId));
+ RevCommit baseCommit = revWalk.next(); // Finds the common commit which is merge base
+ revWalk.dispose();
+ return getRevisionInfo(baseCommit);
+ }
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Failed to get base revision for branch " + branchName + " in project " + this.projectId, e);
+ }
+ }
+
+ @Override
+ public Revision getRevision(String revisionId)
+ {
+ LegendSDLCServerException.validateNonNull(revisionId, "revisionId may not be null");
+ String resolvedRevisionId = resolveRevisionId(revisionId, this);
+ if (resolvedRevisionId == null)
+ {
+ throw new LegendSDLCServerException("Failed to resolve revision " + revisionId + " of project " + this.projectId, Response.Status.NOT_FOUND);
+ }
+ String branchName = getRefBranchName(sourceSpecification);
+ Repository repo = retrieveRepo(this.projectId);
+ try
+ {
+ ObjectId commitId = ObjectId.fromString(resolvedRevisionId);
+ RevWalk revWalk = new RevWalk(repo);
+ RevCommit commit = revWalk.parseCommit(commitId);
+ revWalk.dispose();
+ return getRevisionInfo(commit);
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Failed to get " + resolvedRevisionId + " revision for branch " + branchName + " in project " + this.projectId, e);
+ }
+ }
+
+ @Override
+ public Stream getAllRevisions(Predicate super Revision> predicate, Instant since, Instant until, Integer limit)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ private Revision getRevisionInfo(RevCommit commit)
+ {
+ String revisionID = commit.getId().getName();
+ String authorName = commit.getAuthorIdent().getName();
+ Instant authoredTimeStamp = commit.getAuthorIdent().getWhenAsInstant();
+ String committerName = commit.getCommitterIdent().getName();
+ Instant committedTimeStamp = commit.getCommitterIdent().getWhenAsInstant();
+ String message = commit.getFullMessage();
+ return new FileSystemRevision(revisionID, authorName, authoredTimeStamp, committerName, committedTimeStamp, message);
+ }
+ }
+
+ public class FileSystemFileFileModificationContext implements ProjectFileAccessProvider.FileModificationContext
+ {
+ private final String projectId;
+ private final String revisionId;
+ private final SourceSpecification sourceSpecification;
+
+ public FileSystemFileFileModificationContext(String projectId, SourceSpecification sourceSpecification, String revisionId)
+ {
+ this.projectId = projectId;
+ this.revisionId = revisionId;
+ this.sourceSpecification = Objects.requireNonNull(sourceSpecification, "source specification may not be null");
+ }
+
+ @Override
+ public Revision submit(String message, List extends ProjectFileOperation> operations)
+ {
+ String branchName = getRefBranchName(sourceSpecification);
+ try
+ {
+ Repository repo = retrieveRepo(this.projectId);
+ String referenceRevisionId = this.revisionId;
+ Ref branch = getGitBranch(projectId, branchName);
+ if (referenceRevisionId != null)
+ {
+ String targetBranchRevision = FileSystemRevision.getFileSystemRevision(this.projectId, branchName, repo, branch).getId();
+ if (!referenceRevisionId.equals(targetBranchRevision))
+ {
+ String msg = "Expected " + sourceSpecification + " to be at revision " + referenceRevisionId + "; instead it was at revision " + targetBranchRevision;
+ LOGGER.info(msg);
+ throw new LegendSDLCServerException(msg, Response.Status.CONFLICT);
+ }
+ }
+ Git git = new Git(repo);
+ git.checkout().setName(branchName).call();
+ for (ProjectFileOperation fileOperation : operations)
+ {
+ if (fileOperation instanceof ProjectFileOperation.AddFile)
+ {
+ File newFile = new File(repo.getDirectory().getParent(), fileOperation.getPath());
+ Files.createDirectories(newFile.toPath().getParent());
+ Files.write(newFile.toPath(), ((ProjectFileOperation.AddFile) fileOperation).getContent(), StandardOpenOption.CREATE_NEW);
+ git.add().addFilepattern(".").call();
+ }
+ else if (fileOperation instanceof ProjectFileOperation.ModifyFile)
+ {
+ File file = new File(repo.getDirectory().getParent(), fileOperation.getPath());
+ if (file.exists())
+ {
+ Files.write(file.toPath(), ((ProjectFileOperation.ModifyFile) fileOperation).getNewContent());
+ }
+ else
+ {
+ throw new LegendSDLCServerException("File " + file + " does not exist");
+ }
+ git.add().addFilepattern(".").call();
+ }
+ else if (fileOperation instanceof ProjectFileOperation.DeleteFile)
+ {
+ File fileToRemove = new File(repo.getWorkTree(), fileOperation.getPath().substring(1));
+ if (!fileToRemove.exists())
+ {
+ throw new LegendSDLCServerException("File " + fileToRemove + " does not exist");
+ }
+ fileToRemove.delete();
+ git.rm().addFilepattern(fileOperation.getPath().substring(1)).call();
+ }
+ else
+ {
+ throw new LegendSDLCServerException(fileOperation + "operation is not yet supported");
+ }
+ }
+ git.commit().setMessage(message).call();
+ repo.close();
+ return FileSystemRevision.getFileSystemRevision(projectId, branchName, repo, branch);
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Error occurred while committing changes to " + branchName + " of project " + projectId, e);
+ }
+ }
+ }
+
+ protected ProjectFileAccessProvider getProjectFileAccessProvider()
+ {
+ return new ProjectFileAccessProvider()
+ {
+ @Override
+ public FileAccessContext getFileAccessContext(String projectId, SourceSpecification sourceSpecification, String revisionId)
+ {
+ return new FileSystemFileAccessContext(projectId, sourceSpecification, revisionId);
+ }
+
+ @Override
+ public RevisionAccessContext getRevisionAccessContext(String projectId, SourceSpecification sourceSpecification, Iterable extends String> paths)
+ {
+ return new FileSystemRevisionAccessContext(projectId, sourceSpecification);
+ }
+
+ @Override
+ public FileModificationContext getFileModificationContext(String projectId, SourceSpecification sourceSpecification, String revisionId)
+ {
+ return new FileSystemFileFileModificationContext(projectId, sourceSpecification, revisionId);
+ }
+ };
+ }
+
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemEntityApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemEntityApi.java
new file mode 100644
index 0000000000..be46bb00f2
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/entity/FileSystemEntityApi.java
@@ -0,0 +1,740 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.entity;
+
+import org.eclipse.collections.api.factory.Lists;
+import org.eclipse.collections.api.factory.Maps;
+import org.eclipse.collections.api.list.MutableList;
+import org.eclipse.collections.api.map.MutableMap;
+import org.eclipse.collections.impl.utility.ListIterate;
+import org.eclipse.jgit.lib.*;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.finos.legend.sdlc.domain.model.entity.Entity;
+import org.finos.legend.sdlc.domain.model.entity.change.EntityChange;
+import org.finos.legend.sdlc.domain.model.entity.change.EntityChangeType;
+import org.finos.legend.sdlc.domain.model.revision.Revision;
+import org.finos.legend.sdlc.server.domain.api.entity.EntityAccessContext;
+import org.finos.legend.sdlc.server.domain.api.entity.EntityApi;
+import org.finos.legend.sdlc.server.domain.api.entity.EntityModificationContext;
+import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification;
+import org.finos.legend.sdlc.server.domain.api.project.source.WorkspaceSourceSpecification;
+import org.finos.legend.sdlc.server.error.LegendSDLCServerException;
+import org.finos.legend.sdlc.server.exception.FSException;
+import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider;
+import org.finos.legend.sdlc.server.project.ProjectFileOperation;
+import org.finos.legend.sdlc.server.project.ProjectFiles;
+import org.finos.legend.sdlc.server.project.ProjectStructure;
+import org.finos.legend.sdlc.server.project.CachingFileAccessContext;
+import org.finos.legend.sdlc.server.startup.FSConfiguration;
+import org.finos.legend.sdlc.server.tools.StringTools;
+import org.finos.legend.sdlc.tools.entity.EntityPaths;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import javax.ws.rs.core.Response;
+import java.io.*;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.*;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+public class FileSystemEntityApi extends FileSystemApiWithFileAccess implements EntityApi
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemEntityApi.class);
+
+ @Inject
+ public FileSystemEntityApi(FSConfiguration fsConfiguration)
+ {
+ super(fsConfiguration);
+ }
+
+ @Override
+ public EntityAccessContext getEntityAccessContext(String projectId, SourceSpecification sourceSpecification, String revisionId)
+ {
+ String branchName = getRefBranchName(sourceSpecification);
+ Repository repo = retrieveRepo(projectId);
+ return new FileSystemEntityAccessContext(branchName, repo)
+ {
+ @Override
+ protected ProjectFileAccessProvider.FileAccessContext getFileAccessContext(ProjectFileAccessProvider projectFileAccessProvider)
+ {
+ return projectFileAccessProvider.getFileAccessContext(projectId, sourceSpecification, null);
+ }
+ };
+ }
+
+ @Override
+ public EntityAccessContext getReviewFromEntityAccessContext(String projectId, String reviewId)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public EntityAccessContext getReviewToEntityAccessContext(String projectId, String reviewId)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public EntityModificationContext getEntityModificationContext(String projectId, WorkspaceSourceSpecification sourceSpecification)
+ {
+ return new FileSystemEntityModificationContext(projectId, sourceSpecification);
+ }
+
+
+ public abstract class FileSystemEntityAccessContext implements EntityAccessContext
+ {
+ private final String branchName;
+ private final Repository repo;
+
+ protected FileSystemEntityAccessContext(String branchName, Repository repo)
+ {
+ this.branchName = branchName;
+ this.repo = repo;
+ }
+
+ @Override
+ public Entity getEntity(String path)
+ {
+ ProjectFileAccessProvider.FileAccessContext fileAccessContext = getFileAccessContext(getProjectFileAccessProvider());
+ ProjectStructure projectStructure = ProjectStructure.getProjectStructure(fileAccessContext);
+ for (ProjectStructure.EntitySourceDirectory sourceDirectory : projectStructure.getEntitySourceDirectories())
+ {
+ String filePath = sourceDirectory.entityPathToFilePath(path);
+ ProjectFileAccessProvider.ProjectFile file = fileAccessContext.getFile(filePath);
+ if (file != null)
+ {
+ try
+ {
+ Entity localEntity = sourceDirectory.deserialize(file);
+ if (!Objects.equals(localEntity.getPath(), path))
+ {
+ throw new RuntimeException("Expected entity path " + path + ", found " + localEntity.getPath());
+ }
+ return localEntity;
+ }
+ catch (Exception e)
+ {
+ StringBuilder builder = new StringBuilder("Error deserializing entity \"").append(path).append("\" from file \"").append(filePath).append('"');
+ StringTools.appendThrowableMessageIfPresent(builder, e);
+ throw new LegendSDLCServerException(builder.toString(), e);
+ }
+ }
+ }
+ throw new LegendSDLCServerException("Unknown entity " + path, Response.Status.NOT_FOUND);
+ }
+
+ @Override
+ public List getEntities(Predicate entityPathPredicate, Predicate classifierPathPredicate, Predicate super Map> entityContentPredicate, boolean excludeInvalid)
+ {
+ try (Stream stream = getEntityProjectFiles(getFileAccessContext(getProjectFileAccessProvider()), entityPathPredicate, classifierPathPredicate, entityContentPredicate, excludeInvalid, branchName, repo))
+ {
+ return stream.map(excludeInvalid ? epf ->
+ {
+ try
+ {
+ return epf.getEntity();
+ }
+ catch (Exception ignore)
+ {
+ return null;
+ }
+ } : EntityProjectFile::getEntity).filter(Objects::nonNull).collect(Collectors.toList());
+ }
+ }
+
+ @Override
+ public List getEntityPaths(Predicate entityPathPredicate, Predicate classifierPathPredicate, Predicate super Map> entityContentPredicate)
+ {
+ try (Stream stream = getEntityProjectFiles(getFileAccessContext(getProjectFileAccessProvider()), entityPathPredicate, classifierPathPredicate, entityContentPredicate))
+ {
+ return stream.map(EntityProjectFile::getEntityPath).collect(Collectors.toList());
+ }
+ }
+
+ protected abstract ProjectFileAccessProvider.FileAccessContext getFileAccessContext(ProjectFileAccessProvider projectFileAccessProvider);
+ }
+
+ public class FileSystemEntityModificationContext implements EntityModificationContext
+ {
+ private final String projectId;
+ private final WorkspaceSourceSpecification sourceSpecification;
+
+ private FileSystemEntityModificationContext(String projectId, WorkspaceSourceSpecification sourceSpecification)
+ {
+ this.projectId = projectId;
+ this.sourceSpecification = Objects.requireNonNull(sourceSpecification, "source specification may not be null");
+ }
+
+ @Override
+ public Revision updateEntities(Iterable extends Entity> entities, boolean replace, String message)
+ {
+ LegendSDLCServerException.validateNonNull(entities, "entities may not be null");
+ LegendSDLCServerException.validateNonNull(message, "message may not be null");
+ return FileSystemEntityApi.this.updateEntities(this.projectId, this.sourceSpecification, entities, replace, message);
+ }
+
+ @Override
+ public Revision performChanges(List extends EntityChange> changes, String revisionId, String message)
+ {
+ LegendSDLCServerException.validateNonNull(changes, "changes may not be null");
+ LegendSDLCServerException.validateNonNull(message, "message may not be null");
+ validateEntityChanges(changes);
+ return FileSystemEntityApi.this.performChanges(this.projectId, this.sourceSpecification, revisionId, message, changes);
+ }
+ }
+
+ private static void validateEntityChanges(List extends EntityChange> entityChanges)
+ {
+ StringBuilder builder = new StringBuilder();
+ List errorMessages = Lists.mutable.ofInitialCapacity(4);
+ int i = 0;
+ for (EntityChange change : entityChanges)
+ {
+ collectErrorsForEntityChange(change, errorMessages);
+ if (!errorMessages.isEmpty())
+ {
+ if (builder.length() == 0)
+ {
+ builder.append("There are entity change errors:");
+ }
+ builder.append("\n\tEntity change #").append(i + 1).append(" (").append(change).append("):");
+ errorMessages.forEach(m -> builder.append("\n\t\t").append(m));
+ errorMessages.clear();
+ }
+ i++;
+ }
+ if (builder.length() > 0)
+ {
+ throw new LegendSDLCServerException(builder.toString(), Response.Status.BAD_REQUEST);
+ }
+ }
+
+ private static void collectErrorsForEntityChange(EntityChange entityChange, Collection super String> errorMessages)
+ {
+ if (entityChange == null)
+ {
+ errorMessages.add("Invalid entity change: null");
+ return;
+ }
+
+ EntityChangeType type = entityChange.getType();
+ if (type == null)
+ {
+ errorMessages.add("Missing entity change type");
+ }
+
+ String path = entityChange.getEntityPath();
+ String classifierPath = entityChange.getClassifierPath();
+ Map content = entityChange.getContent();
+ String newPath = entityChange.getNewEntityPath();
+
+ if (path == null)
+ {
+ errorMessages.add("Missing entity path");
+ }
+ else if (!EntityPaths.isValidEntityPath(path))
+ {
+ errorMessages.add("Invalid entity path: " + path);
+ }
+ else if (content != null)
+ {
+ Object pkg = content.get("package");
+ Object name = content.get("name");
+ if (!(pkg instanceof String) || !(name instanceof String) || !path.equals(pkg + EntityPaths.PACKAGE_SEPARATOR + name))
+ {
+ StringBuilder builder = new StringBuilder("Mismatch between entity path (\"").append(path).append("\") and package (");
+ if (pkg instanceof String)
+ {
+ builder.append('"').append(pkg).append('"');
+ }
+ else
+ {
+ builder.append(pkg);
+ }
+ builder.append(") and name (");
+ if (name instanceof String)
+ {
+ builder.append('"').append(name).append('"');
+ }
+ else
+ {
+ builder.append(name);
+ }
+ builder.append(") properties");
+ errorMessages.add(builder.toString());
+ }
+ }
+ if (type != null)
+ {
+ switch (type)
+ {
+ case CREATE:
+ case MODIFY:
+ {
+ if (classifierPath == null)
+ {
+ errorMessages.add("Missing classifier path");
+ }
+ else if (!EntityPaths.isValidClassifierPath(classifierPath))
+ {
+ errorMessages.add("Invalid classifier path: " + classifierPath);
+ }
+ if (content == null)
+ {
+ errorMessages.add("Missing content");
+ }
+ if (newPath != null)
+ {
+ errorMessages.add("Unexpected new entity path: " + newPath);
+ }
+ break;
+ }
+ case RENAME:
+ {
+ if (classifierPath != null)
+ {
+ errorMessages.add("Unexpected classifier path: " + classifierPath);
+ }
+ if (content != null)
+ {
+ errorMessages.add("Unexpected content");
+ }
+ if (newPath == null)
+ {
+ errorMessages.add("Missing new entity path");
+ }
+ else if (!EntityPaths.isValidEntityPath(newPath))
+ {
+ errorMessages.add("Invalid new entity path: " + newPath);
+ }
+ break;
+ }
+ case DELETE:
+ {
+ if (classifierPath != null)
+ {
+ errorMessages.add("Unexpected classifier path: " + classifierPath);
+ }
+ if (content != null)
+ {
+ errorMessages.add("Unexpected content");
+ }
+ if (newPath != null)
+ {
+ errorMessages.add("Unexpected new entity path: " + newPath);
+ }
+ break;
+ }
+ default:
+ {
+ errorMessages.add("Unexpected entity change type: " + type);
+ }
+ }
+ }
+ }
+
+ public Revision updateEntities(String projectId, WorkspaceSourceSpecification sourceSpecification, Iterable extends Entity> newEntities, boolean replace, String message)
+ {
+ MutableMap newEntityDefinitions = Maps.mutable.empty();
+ MutableList errorMessages = Lists.mutable.empty();
+ newEntities.forEach(entity ->
+ {
+ if (entity == null)
+ {
+ errorMessages.add("Invalid entity: null");
+ return;
+ }
+
+ String path = entity.getPath();
+ if (!EntityPaths.isValidEntityPath(path))
+ {
+ errorMessages.add("Invalid entity path: \"" + path + "\"");
+ return;
+ }
+
+ String classifierPath = entity.getClassifierPath();
+ if (!EntityPaths.isValidClassifierPath(classifierPath))
+ {
+ errorMessages.add("Entity: " + path + "; error: invalid classifier path \"" + classifierPath + "\"");
+ }
+
+ Map content = entity.getContent();
+ if (content == null)
+ {
+ errorMessages.add("Entity: " + path + "; error: missing content");
+ }
+ else if (path != null)
+ {
+ Object pkg = content.get("package");
+ Object name = content.get("name");
+ if (!(pkg instanceof String) || !(name instanceof String) || !path.equals(pkg + EntityPaths.PACKAGE_SEPARATOR + name))
+ {
+ StringBuilder builder = new StringBuilder("Entity: ").append(path).append("; mismatch between entity path and package (");
+ if (pkg instanceof String)
+ {
+ builder.append('"').append(pkg).append('"');
+ }
+ else
+ {
+ builder.append(pkg);
+ }
+ builder.append(") and name (");
+ if (name instanceof String)
+ {
+ builder.append('"').append(name).append('"');
+ }
+ else
+ {
+ builder.append(name);
+ }
+ builder.append(") properties");
+ errorMessages.add(builder.toString());
+ }
+ }
+
+ Entity oldDefinition = newEntityDefinitions.put(path, entity);
+ if (oldDefinition != null)
+ {
+ errorMessages.add("Entity: " + path + "; error: multiple definitions");
+ }
+ });
+ if (errorMessages.notEmpty())
+ {
+ throw new LegendSDLCServerException((errorMessages.size() == 1) ? errorMessages.get(0) : "There are errors with entity definitions:\n\t" + String.join("\n\t", errorMessages), Response.Status.BAD_REQUEST);
+ }
+
+ ProjectFileAccessProvider fileProvider = getProjectFileAccessProvider();
+ Revision currentWorkspaceRevision = fileProvider.getRevisionAccessContext(projectId, sourceSpecification, null).getCurrentRevision();
+ if (currentWorkspaceRevision == null)
+ {
+ throw new LegendSDLCServerException("Could not find current revision for " + sourceSpecification + ": it may be corrupt");
+ }
+ String revisionId = currentWorkspaceRevision.getId();
+ LOGGER.debug("Using revision {} for reference in entity update in {} in project {}", revisionId, sourceSpecification, projectId);
+ List entityChanges = Lists.mutable.ofInitialCapacity(newEntityDefinitions.size());
+ if (newEntityDefinitions.isEmpty())
+ {
+ if (replace)
+ {
+ try (Stream stream = getEntityProjectFiles(fileProvider.getFileAccessContext(projectId, sourceSpecification, revisionId)))
+ {
+ stream.map(EntityProjectFile::getEntityPath).map(EntityChange::newDeleteEntity).forEach(entityChanges::add);
+ }
+ }
+ }
+ else
+ {
+ try (Stream stream = getEntityProjectFiles(fileProvider.getFileAccessContext(projectId, sourceSpecification, revisionId)))
+ {
+ stream.forEach(epf ->
+ {
+ String path = epf.getEntityPath();
+ Entity newDefinition = newEntityDefinitions.remove(path);
+ if (newDefinition == null)
+ {
+ if (replace)
+ {
+ entityChanges.add(EntityChange.newDeleteEntity(path));
+ }
+ }
+ else
+ {
+ Entity entity = epf.getEntity();
+ String newClassifierPath = newDefinition.getClassifierPath();
+ Map newContent = newDefinition.getContent();
+ if (!newClassifierPath.equals(entity.getClassifierPath()) || !newContent.equals(entity.getContent()))
+ {
+ entityChanges.add(EntityChange.newModifyEntity(path, newClassifierPath, newContent));
+ }
+ }
+ });
+ }
+ newEntityDefinitions.forEachValue(definition -> entityChanges.add(EntityChange.newCreateEntity(definition.getPath(), definition.getClassifierPath(), definition.getContent())));
+ }
+
+ return performChanges(projectId, sourceSpecification, revisionId, message, entityChanges);
+ }
+
+ private Stream getEntityProjectFiles(ProjectFileAccessProvider.FileAccessContext accessContext, Predicate entityPathPredicate, Predicate classifierPathPredicate, Predicate super Map> contentPredicate)
+ {
+ return getEntityProjectFiles(accessContext, entityPathPredicate, classifierPathPredicate, contentPredicate, false, null, null);
+ }
+
+ private Stream getEntityProjectFiles(ProjectFileAccessProvider.FileAccessContext accessContext, Predicate entityPathPredicate, Predicate classifierPathPredicate, Predicate super Map> contentPredicate, boolean excludeInvalid, String branchName, Repository repo)
+ {
+ Stream stream = (repo != null && branchName != null) ? getEntityProjectFiles(branchName, repo, accessContext) : getEntityProjectFiles(accessContext);
+ if (entityPathPredicate != null)
+ {
+ stream = stream.filter(epf -> entityPathPredicate.test(epf.getEntityPath()));
+ }
+ if (classifierPathPredicate != null)
+ {
+ stream = stream.filter(excludeInvalid ? epf ->
+ {
+ Entity entity;
+ try
+ {
+ entity = epf.getEntity();
+ }
+ catch (Exception ignore)
+ {
+ return false;
+ }
+ return classifierPathPredicate.test(entity.getClassifierPath());
+ } : epf -> classifierPathPredicate.test(epf.getEntity().getClassifierPath()));
+ }
+ if (contentPredicate != null)
+ {
+ stream = stream.filter(excludeInvalid ? epf ->
+ {
+ Entity entity;
+ try
+ {
+ entity = epf.getEntity();
+ }
+ catch (Exception ignore)
+ {
+ return false;
+ }
+ return contentPredicate.test(entity.getContent());
+ } : epf -> contentPredicate.test(epf.getEntity().getContent()));
+ }
+ return stream;
+ }
+
+ private Stream getEntityProjectFiles(String branchName, Repository repo, ProjectFileAccessProvider.FileAccessContext fileAccessContext)
+ {
+ ProjectStructure projectStructure = ProjectStructure.getProjectStructure(fileAccessContext);
+ List sourceDirectories = projectStructure.getEntitySourceDirectories();
+ return sourceDirectories.stream().flatMap(sd -> getSourceDirectoryProjectFiles(sd, branchName, repo, projectStructure.getProjectConfiguration().getProjectId()));
+ }
+
+ private Stream getSourceDirectoryProjectFiles(ProjectStructure.EntitySourceDirectory sourceDirectory, String workspaceId, Repository repo, String projectID)
+ {
+ List files = new ArrayList<>();
+ try
+ {
+ ObjectId headCommitId = repo.findRef(workspaceId).getObjectId();
+ TreeWalk treeWalk = new TreeWalk(repo);
+ treeWalk.addTree(repo.parseCommit(headCommitId).getTree());
+ treeWalk.setRecursive(true);
+ while (treeWalk.next())
+ {
+ File file = new File(repo.getWorkTree(), treeWalk.getPathString());
+ ObjectId entityId = treeWalk.getObjectId(0);
+ ObjectLoader loader = repo.open(entityId);
+ byte[] entityContentBytes = loader.getBytes();
+ String entityContent = new String(entityContentBytes, StandardCharsets.UTF_8);
+ ProjectFileAccessProvider.ProjectFile projectFile = ProjectFiles.newStringProjectFile(file.getCanonicalPath(), entityContent);
+ if (sourceDirectory.isPossiblyEntityFilePath(getEntityPath(file, projectID)))
+ {
+ files.add(new EntityProjectFile(sourceDirectory, projectFile, projectID, getRootDirectory()));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LOGGER.error("Error occurred while parsing Git commit for workspace {}", workspaceId, e);
+ throw FSException.getLegendSDLCServerException("Failed to get project files for workspace " + workspaceId + " of project " + projectID, e);
+ }
+ return files.stream();
+ }
+
+ private String getEntityPath(File file, String projectID)
+ {
+ Path filePath = Paths.get(file.getPath());
+ Path pathToProject = Paths.get(getRootDirectory() + "/" + projectID);
+ if (!filePath.startsWith(pathToProject))
+ {
+ throw new LegendSDLCServerException("Paths " + filePath + " and " + pathToProject + " are not related");
+ }
+ Path relativePath = filePath.subpath(pathToProject.getNameCount(), filePath.getNameCount());
+ return "/" + relativePath;
+ }
+
+ private Stream getEntityProjectFiles(ProjectFileAccessProvider.FileAccessContext accessContext)
+ {
+ ProjectStructure projectStructure = ProjectStructure.getProjectStructure(accessContext);
+ List sourceDirectories = projectStructure.getEntitySourceDirectories();
+ ProjectFileAccessProvider.FileAccessContext cachingAccessContext = (sourceDirectories.size() > 1) ? CachingFileAccessContext.wrap(accessContext) : accessContext;
+ return sourceDirectories.stream().flatMap(sd -> getSourceDirectoryProjectFiles(cachingAccessContext, sd, projectStructure.getProjectConfiguration().getProjectId()));
+ }
+
+ private Stream getSourceDirectoryProjectFiles(ProjectFileAccessProvider.FileAccessContext accessContext, ProjectStructure.EntitySourceDirectory sourceDirectory, String projectID)
+ {
+ return accessContext.getFilesInDirectory(sourceDirectory.getDirectory())
+ .filter(f -> sourceDirectory.isPossiblyEntityFilePath(f.getPath()))
+ .map(f -> new EntityProjectFile(sourceDirectory, f, projectID, getRootDirectory()));
+ }
+
+ private Revision performChanges(String projectId, WorkspaceSourceSpecification sourceSpecification, String referenceRevisionId, String message, List extends EntityChange> changes)
+ {
+ int changeCount = changes.size();
+ if (changeCount == 0)
+ {
+ LOGGER.debug("No changes for {} in project {}", sourceSpecification, projectId);
+ return null;
+ }
+ LOGGER.debug("Committing {} changes to {} in project {}: {}", changeCount, sourceSpecification, projectId, message);
+ ProjectFileAccessProvider.FileAccessContext fileAccessContext = getProjectFileAccessProvider().getFileAccessContext(projectId, sourceSpecification, referenceRevisionId);
+ ProjectStructure projectStructure = ProjectStructure.getProjectStructure(fileAccessContext);
+ MutableList fileOperations = ListIterate.collect(changes, c -> entityChangeToFileOperation(c, projectStructure, fileAccessContext));
+ fileOperations.removeIf(Objects::isNull);
+ if (fileOperations.isEmpty())
+ {
+ LOGGER.debug("No changes for {} in project {}", sourceSpecification, projectId);
+ return null;
+ }
+ return getProjectFileAccessProvider().getFileModificationContext(projectId, sourceSpecification, referenceRevisionId).submit(message, fileOperations);
+ }
+
+ private ProjectFileOperation entityChangeToFileOperation(EntityChange change, ProjectStructure projectStructure, ProjectFileAccessProvider.FileAccessContext fileAccessContext)
+ {
+ switch (change.getType())
+ {
+ case CREATE:
+ {
+ String entityPath = change.getEntityPath();
+
+ // check if a file already exists for this entity
+ if (projectStructure.findEntityFile(entityPath, fileAccessContext) != null)
+ {
+ throw new LegendSDLCServerException("Unable to handle operation " + change + ": entity \"" + entityPath + "\" already exists");
+ }
+
+ Entity entity = Entity.newEntity(entityPath, change.getClassifierPath(), change.getContent());
+ ProjectStructure.EntitySourceDirectory sourceDirectory = projectStructure.findSourceDirectoryForEntity(entity);
+ if (sourceDirectory == null)
+ {
+ throw new LegendSDLCServerException("Unable to handle operation " + change + ": cannot serialize entity \"" + entityPath + "\"");
+ }
+ return ProjectFileOperation.addFile(sourceDirectory.entityPathToFilePath(change.getEntityPath()), sourceDirectory.serializeToBytes(entity));
+ }
+ case DELETE:
+ {
+ String entityPath = change.getEntityPath();
+ String filePath = projectStructure.findEntityFile(entityPath, fileAccessContext);
+ if (filePath == null)
+ {
+ throw new LegendSDLCServerException("Unable to handle operation " + change + ": could not find entity \"" + entityPath + "\"");
+ }
+ return ProjectFileOperation.deleteFile(filePath);
+ }
+ case MODIFY:
+ {
+ String entityPath = change.getEntityPath();
+ Entity entity = Entity.newEntity(entityPath, change.getClassifierPath(), change.getContent());
+
+ // find current file
+ String currentFilePath = projectStructure.findEntityFile(entityPath, fileAccessContext);
+ if (currentFilePath == null)
+ {
+ throw new LegendSDLCServerException("Unable to handle operation " + change + ": could not find entity \"" + entityPath + "\"");
+ }
+
+ ProjectStructure.EntitySourceDirectory newSourceDirectory = projectStructure.findSourceDirectoryForEntity(entity);
+ if (newSourceDirectory == null)
+ {
+ throw new LegendSDLCServerException("Unable to handle operation " + change + ": cannot serialize entity \"" + entityPath + "\"");
+ }
+
+ String newFilePath = newSourceDirectory.entityPathToFilePath(entityPath);
+ byte[] serialized = newSourceDirectory.serializeToBytes(entity);
+
+ if (!currentFilePath.equals(newFilePath))
+ {
+ return ProjectFileOperation.moveFile(currentFilePath, newFilePath, serialized);
+ }
+ if (!Arrays.equals(serialized, fileAccessContext.getFile(currentFilePath).getContentAsBytes()))
+ {
+ return ProjectFileOperation.modifyFile(currentFilePath, serialized);
+ }
+ return null;
+ }
+ case RENAME:
+ {
+ String currentEntityPath = change.getEntityPath();
+ for (ProjectStructure.EntitySourceDirectory sourceDirectory : projectStructure.getEntitySourceDirectories())
+ {
+ String filePath = sourceDirectory.entityPathToFilePath(currentEntityPath);
+ if (fileAccessContext.fileExists(filePath))
+ {
+ String newFilePath = sourceDirectory.entityPathToFilePath(change.getNewEntityPath());
+ return ProjectFileOperation.moveFile(filePath, newFilePath);
+ }
+ }
+ throw new LegendSDLCServerException("Unable to handle operation " + change + ": could not find entity \"" + currentEntityPath + "\"");
+ }
+ default:
+ {
+ throw new RuntimeException("Unknown entity change type: " + change.getType());
+ }
+ }
+ }
+
+ static class EntityProjectFile
+ {
+ private final ProjectStructure.EntitySourceDirectory sourceDirectory;
+ private final ProjectFileAccessProvider.ProjectFile file;
+ private String path;
+ private Entity entity;
+ private String projectID;
+ private String rootDirectory;
+
+ private EntityProjectFile(ProjectStructure.EntitySourceDirectory sourceDirectory, ProjectFileAccessProvider.ProjectFile file, String projectID, String rootDirectory)
+ {
+ this.sourceDirectory = sourceDirectory;
+ this.file = file;
+ this.projectID = projectID;
+ this.rootDirectory = rootDirectory;
+ }
+
+ synchronized String getEntityPath()
+ {
+ if (this.path == null)
+ {
+ Path filePath = Paths.get(this.file.getPath());
+ Path pathToProject = Paths.get(rootDirectory + "/" + this.projectID);
+ if (!filePath.startsWith(pathToProject))
+ {
+ throw new LegendSDLCServerException("Paths " + filePath + " and " + pathToProject + " are not related");
+ }
+ Path relativePath = filePath.subpath(pathToProject.getNameCount(), filePath.getNameCount());
+ this.path = this.sourceDirectory.filePathToEntityPath("/" + relativePath);
+ }
+ return this.path;
+ }
+
+ synchronized Entity getEntity()
+ {
+ if (this.entity == null)
+ {
+ Entity localEntity = this.sourceDirectory.deserialize(this.file);
+ if (!Objects.equals(localEntity.getPath(), getEntityPath()))
+ {
+ throw new RuntimeException("Expected entity path " + getEntityPath() + ", found " + localEntity.getPath());
+ }
+ this.entity = localEntity;
+ }
+ return this.entity;
+ }
+ }
+
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/issue/FileSystemIssueApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/issue/FileSystemIssueApi.java
new file mode 100644
index 0000000000..ba6d9f8b48
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/issue/FileSystemIssueApi.java
@@ -0,0 +1,54 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.issue;
+
+import org.finos.legend.sdlc.domain.model.issue.Issue;
+import org.finos.legend.sdlc.server.domain.api.issue.IssueApi;
+import org.finos.legend.sdlc.server.exception.FSException;
+
+import javax.inject.Inject;
+import java.util.List;
+
+public class FileSystemIssueApi implements IssueApi
+{
+ @Inject
+ public FileSystemIssueApi()
+ {
+ }
+
+ @Override
+ public Issue getIssue(String projectId, String issueId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public List getIssues(String projectId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Issue createIssue(String projectId, String title, String description)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public void deleteIssue(String projectId, String issueId)
+ {
+ throw FSException.unavailableFeature();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/patch/FileSystemPatchApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/patch/FileSystemPatchApi.java
new file mode 100644
index 0000000000..612afc6015
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/patch/FileSystemPatchApi.java
@@ -0,0 +1,56 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.patch;
+
+import org.finos.legend.sdlc.domain.model.patch.Patch;
+import org.finos.legend.sdlc.domain.model.version.Version;
+import org.finos.legend.sdlc.domain.model.version.VersionId;
+import org.finos.legend.sdlc.server.domain.api.patch.PatchApi;
+import org.finos.legend.sdlc.server.exception.FSException;
+
+import javax.inject.Inject;
+import java.util.List;
+
+public class FileSystemPatchApi implements PatchApi
+{
+ @Inject
+ public FileSystemPatchApi()
+ {
+ }
+
+ @Override
+ public Patch newPatch(String projectId, VersionId sourceVersion)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public List getPatches(String projectId, Integer minMajorVersion, Integer maxMajorVersion, Integer minMinorVersion, Integer maxMinorVersion, Integer minPatchVersion, Integer maxPatchVersion)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public void deletePatch(String projectId, VersionId patchReleaseVersionId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Version releasePatch(String projectId, VersionId patchReleaseVersionId)
+ {
+ throw FSException.unavailableFeature();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectApi.java
new file mode 100644
index 0000000000..cea10c45a1
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectApi.java
@@ -0,0 +1,277 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.project;
+
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.finos.legend.sdlc.domain.model.project.Project;
+import org.finos.legend.sdlc.domain.model.project.ProjectType;
+import org.finos.legend.sdlc.domain.model.project.accessRole.AccessRole;
+import org.finos.legend.sdlc.domain.model.project.accessRole.AuthorizableProjectAction;
+import org.finos.legend.sdlc.server.api.entity.FileSystemApiWithFileAccess;
+import org.finos.legend.sdlc.server.domain.api.project.ProjectApi;
+import org.finos.legend.sdlc.server.domain.api.project.ProjectConfigurationUpdater;
+import org.finos.legend.sdlc.server.error.LegendSDLCServerException;
+import org.finos.legend.sdlc.server.exception.FSException;
+import org.finos.legend.sdlc.server.project.ProjectStructure;
+import org.finos.legend.sdlc.server.project.ProjectStructurePlatformExtensions;
+import org.finos.legend.sdlc.server.project.config.ProjectCreationConfiguration;
+import org.finos.legend.sdlc.server.project.config.ProjectStructureConfiguration;
+import org.finos.legend.sdlc.server.project.extension.ProjectStructureExtensionProvider;
+
+import org.eclipse.jgit.api.Git;
+import org.finos.legend.sdlc.server.startup.FSConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.inject.Inject;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
+
+public class FileSystemProjectApi extends FileSystemApiWithFileAccess implements ProjectApi
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemProjectApi.class);
+ private final ProjectStructureConfiguration projectStructureConfig;
+ private final ProjectStructureExtensionProvider projectStructureExtensionProvider;
+ private final ProjectStructurePlatformExtensions projectStructurePlatformExtensions;
+
+ @Inject
+ public FileSystemProjectApi(FSConfiguration fsConfiguration, ProjectStructureConfiguration projectStructureConfig, ProjectStructureExtensionProvider projectStructureExtensionProvider, ProjectStructurePlatformExtensions projectStructurePlatformExtensions)
+ {
+ super(fsConfiguration);
+ this.projectStructureConfig = projectStructureConfig;
+ this.projectStructureExtensionProvider = projectStructureExtensionProvider;
+ this.projectStructurePlatformExtensions = projectStructurePlatformExtensions;
+ }
+
+ @Override
+ public Project getProject(String id)
+ {
+ Repository repository = retrieveRepo(id);
+ Git git = new Git(repository);
+ return gitProjectToProject(git);
+ }
+
+ @Override
+ public List getProjects(boolean user, String search, Iterable tags, Integer limit)
+ {
+ List gitRepos = new ArrayList<>();
+ try (Stream paths = Files.list(Paths.get(getRootDirectory())))
+ {
+ paths.filter(path -> Files.isDirectory(path)).forEach(repoDir ->
+ {
+ try
+ {
+ FileRepositoryBuilder repoBuilder = new FileRepositoryBuilder();
+ Repository repository = repoBuilder.setGitDir(new File(repoDir.toFile(), ".git")).readEnvironment().findGitDir().build();
+ if (repository != null)
+ {
+ gitRepos.add(gitProjectToProject(new Git(repository)));
+ }
+ }
+ catch (IOException e)
+ {
+ LOGGER.error("Repository {} could not be accessed", repoDir, e);
+ }
+ });
+ }
+ catch (IOException e)
+ {
+ LOGGER.error("Exception occurred when opening the directory {}", getRootDirectory(), e);
+ throw FSException.getLegendSDLCServerException("Failed to fetch projects", e);
+ }
+ return gitRepos;
+ }
+
+ @Override
+ public Project createProject(String name, String description, ProjectType type, String groupId, String artifactId, Iterable tags)
+ {
+ LegendSDLCServerException.validate(name, n -> (n != null) && !n.isEmpty(), "name may not be null or empty");
+ LegendSDLCServerException.validateNonNull(description, "description may not be null");
+
+ String projectPath = getRootDirectory() + "/" + name;
+ Git gitProject;
+ String projectId = name;
+ try
+ {
+ Repository repository = FileRepositoryBuilder.create(new File(projectPath, ".git"));
+ repository.create();
+
+ gitProject = new Git(repository);
+ gitProject.commit().setMessage("Initial Commit").call();
+
+ repository.getConfig().setString("project", null, "id", projectId);
+ repository.getConfig().setString("project", null, "name", name);
+ repository.getConfig().setString("project", null, "description", description);
+ repository.getConfig().save();
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Failed to create project: " + name, e);
+ }
+ Project project = gitProjectToProject(gitProject);
+
+ // Build project structure
+ int projectStructureVersion = getDefaultProjectStructureVersion();
+ ProjectConfigurationUpdater configUpdater = ProjectConfigurationUpdater.newUpdater()
+ .withProjectId(project.getProjectId())
+ .withProjectType(type)
+ .withGroupId(groupId)
+ .withArtifactId(artifactId)
+ .withProjectStructureVersion(projectStructureVersion);
+ if (this.projectStructureExtensionProvider != null && type != ProjectType.EMBEDDED)
+ {
+ configUpdater.setProjectStructureExtensionVersion(this.projectStructureExtensionProvider.getLatestVersionForProjectStructureVersion(projectStructureVersion));
+ }
+ ProjectStructure.newUpdateBuilder(getProjectFileAccessProvider(), project.getProjectId(), configUpdater)
+ .withMessage("Build project structure")
+ .withProjectStructureExtensionProvider(this.projectStructureExtensionProvider)
+ .withProjectStructurePlatformExtensions(this.projectStructurePlatformExtensions)
+ .build();
+ return project;
+ }
+
+ private int getDefaultProjectStructureVersion()
+ {
+ ProjectCreationConfiguration projectCreationConfig = getProjectCreationConfiguration();
+ if (projectCreationConfig != null)
+ {
+ Integer defaultProjectStructureVersion = projectCreationConfig.getDefaultProjectStructureVersion();
+ if (defaultProjectStructureVersion != null)
+ {
+ return defaultProjectStructureVersion;
+ }
+ }
+ return ProjectStructure.getLatestProjectStructureVersion();
+ }
+
+ private ProjectCreationConfiguration getProjectCreationConfiguration()
+ {
+ return (this.projectStructureConfig == null) ? null : this.projectStructureConfig.getProjectCreationConfiguration();
+ }
+
+ private Project gitProjectToProject(Git project)
+ {
+ return (project == null) ? null : new FileSystemProjectApi.ProjectWrapper(project);
+ }
+
+ private static class ProjectWrapper implements Project
+ {
+ private final Git gitProject;
+
+ public ProjectWrapper(Git gitProject)
+ {
+ this.gitProject = gitProject;
+ }
+
+ @Override
+ public String getProjectId()
+ {
+ return this.gitProject.getRepository().getConfig().getString("project", null, "id");
+ }
+
+ @Override
+ public String getName()
+ {
+ return this.gitProject.getRepository().getConfig().getString("project", null, "name");
+ }
+
+ @Override
+ public String getDescription()
+ {
+ return this.gitProject.getRepository().getConfig().getString("project", null, "description");
+ }
+
+ @Override
+ public List getTags()
+ {
+ return null;
+ }
+
+ @Override
+ public ProjectType getProjectType()
+ {
+ return null;
+ }
+
+ @Override
+ public String getWebUrl()
+ {
+ return null;
+ }
+ }
+
+ @Override
+ public void deleteProject(String id)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public void changeProjectName(String id, String newName)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public void changeProjectDescription(String id, String newDescription)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public void updateProjectTags(String id, Iterable tagsToRemove, Iterable tagsToAdd)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public void setProjectTags(String id, Iterable tags)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public AccessRole getCurrentUserAccessRole(String id)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Set checkUserAuthorizedActions(String id, Set actions)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public boolean checkUserAuthorizedAction(String id, AuthorizableProjectAction action)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public ImportReport importProject(String id, ProjectType type, String groupId, String artifactId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectConfigurationApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectConfigurationApi.java
new file mode 100644
index 0000000000..a07025fbc2
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/project/FileSystemProjectConfigurationApi.java
@@ -0,0 +1,106 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.project;
+
+import org.finos.legend.sdlc.domain.model.project.configuration.ArtifactTypeGenerationConfiguration;
+import org.finos.legend.sdlc.domain.model.project.configuration.ProjectConfiguration;
+import org.finos.legend.sdlc.domain.model.project.configuration.ProjectStructureVersion;
+import org.finos.legend.sdlc.domain.model.revision.Revision;
+import org.finos.legend.sdlc.server.api.entity.FileSystemApiWithFileAccess;
+import org.finos.legend.sdlc.server.domain.api.project.ProjectConfigurationApi;
+import org.finos.legend.sdlc.server.domain.api.project.ProjectConfigurationUpdater;
+import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification;
+import org.finos.legend.sdlc.server.domain.api.project.source.WorkspaceSourceSpecification;
+import org.finos.legend.sdlc.server.error.LegendSDLCServerException;
+import org.finos.legend.sdlc.server.project.ProjectConfigurationStatusReport;
+import org.finos.legend.sdlc.server.project.ProjectStructure;
+import org.finos.legend.sdlc.server.project.extension.ProjectStructureExtensionProvider;
+import org.finos.legend.sdlc.server.startup.FSConfiguration;
+
+import java.util.Collections;
+import java.util.List;
+import javax.inject.Inject;
+
+public class FileSystemProjectConfigurationApi extends FileSystemApiWithFileAccess implements ProjectConfigurationApi
+{
+ private final ProjectStructureExtensionProvider projectStructureExtensionProvider;
+
+ @Inject
+ public FileSystemProjectConfigurationApi(FSConfiguration fsConfiguration, ProjectStructureExtensionProvider projectStructureExtensionProvider)
+ {
+ super(fsConfiguration);
+ this.projectStructureExtensionProvider = projectStructureExtensionProvider;
+ }
+
+ @Override
+ public ProjectConfiguration getProjectConfiguration(String projectId, SourceSpecification sourceSpecification, String revisionId)
+ {
+ LegendSDLCServerException.validateNonNull(projectId, "projectId may not be null");
+ LegendSDLCServerException.validateNonNull(sourceSpecification, "sourceSpecification may not be null");
+ ProjectConfiguration config = ProjectStructure.getProjectConfiguration(projectId, sourceSpecification, revisionId, getProjectFileAccessProvider());
+ return (config == null) ? ProjectStructure.getDefaultProjectConfiguration(projectId) : config;
+ }
+
+ @Override
+ public ProjectConfiguration getReviewFromProjectConfiguration(String projectId, String reviewId)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public ProjectConfiguration getReviewToProjectConfiguration(String projectId, String reviewId)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public Revision updateProjectConfiguration(String projectId, WorkspaceSourceSpecification sourceSpecification, String message, ProjectConfigurationUpdater updater)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public List getAvailableArtifactGenerations(String projectId, SourceSpecification sourceSpecification, String revisionId)
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public ProjectConfigurationStatusReport getProjectConfigurationStatus(String projectId)
+ {
+ boolean isProjectConfigured = (ProjectStructure.getProjectConfiguration(projectId, SourceSpecification.projectSourceSpecification(), null, getProjectFileAccessProvider()) != null);
+ return new ProjectConfigurationStatusReport()
+ {
+ @Override
+ public boolean isProjectConfigured()
+ {
+ return isProjectConfigured;
+ }
+
+ @Override
+ public List getReviewIds()
+ {
+ return Collections.emptyList();
+ }
+ };
+ }
+
+ @Override
+ public ProjectStructureVersion getLatestProjectStructureVersion()
+ {
+ int latestProjectStructureVersion = ProjectStructure.getLatestProjectStructureVersion();
+ return ProjectStructureVersion.newProjectStructureVersion(latestProjectStructureVersion, this.projectStructureExtensionProvider.getLatestVersionForProjectStructureVersion(latestProjectStructureVersion));
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/review/FileSystemReviewApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/review/FileSystemReviewApi.java
new file mode 100644
index 0000000000..ff7bf2d246
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/review/FileSystemReviewApi.java
@@ -0,0 +1,121 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.review;
+
+import org.finos.legend.sdlc.domain.model.project.workspace.WorkspaceType;
+import org.finos.legend.sdlc.domain.model.review.Approval;
+import org.finos.legend.sdlc.domain.model.review.Review;
+import org.finos.legend.sdlc.domain.model.review.ReviewState;
+import org.finos.legend.sdlc.server.domain.api.review.ReviewApi;
+import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSpecification;
+import org.finos.legend.sdlc.server.exception.FSException;
+
+import javax.inject.Inject;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+import java.util.function.BiPredicate;
+
+public class FileSystemReviewApi implements ReviewApi
+{
+ @Inject
+ public FileSystemReviewApi()
+ {
+ }
+
+ @Override
+ public Review getReview(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public List getReviews(String projectId, ReviewState state, Iterable revisionIds, BiPredicate workspaceIdAndTypePredicate, Instant since, Instant until, Integer limit)
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getReviews(boolean assignedToMe, boolean authoredByMe, List labels, BiPredicate workspaceIdAndTypePredicate, ReviewState state, Instant since, Instant until, Integer limit)
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Review createReview(String projectId, WorkspaceSpecification workspaceSpecification, String title, String description, List labels)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Review editReview(String projectId, String reviewId, String title, String description, List labels)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Review closeReview(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Review reopenReview(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Review approveReview(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Review revokeReviewApproval(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Review rejectReview(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Approval getReviewApproval(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Review commitReview(String projectId, String reviewId, String message)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public ReviewUpdateStatus getReviewUpdateStatus(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public ReviewUpdateStatus updateReview(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/revision/FileSystemRevisionApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/revision/FileSystemRevisionApi.java
new file mode 100644
index 0000000000..76cd194bf4
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/revision/FileSystemRevisionApi.java
@@ -0,0 +1,167 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.revision;
+
+import org.finos.legend.sdlc.domain.model.revision.Revision;
+import org.finos.legend.sdlc.domain.model.revision.RevisionStatus;
+import org.finos.legend.sdlc.server.api.entity.FileSystemApiWithFileAccess;
+import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification;
+import org.finos.legend.sdlc.server.domain.api.revision.RevisionAccessContext;
+import org.finos.legend.sdlc.server.domain.api.revision.RevisionApi;
+import org.finos.legend.sdlc.server.error.LegendSDLCServerException;
+import org.finos.legend.sdlc.server.exception.FSException;
+import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider;
+import org.finos.legend.sdlc.server.startup.FSConfiguration;
+
+import javax.inject.Inject;
+import java.time.Instant;
+import java.util.List;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+
+public class FileSystemRevisionApi extends FileSystemApiWithFileAccess implements RevisionApi
+{
+ @Inject
+ public FileSystemRevisionApi(FSConfiguration fsConfiguration)
+ {
+ super(fsConfiguration);
+ }
+
+ @Override
+ public RevisionAccessContext getRevisionContext(String projectId, SourceSpecification sourceSpec)
+ {
+ return new ProjectFileRevisionAccessContextWrapper(getProjectFileAccessProvider().getRevisionAccessContext(projectId, sourceSpec));
+ }
+
+ @Override
+ public RevisionAccessContext getPackageRevisionContext(String projectId, SourceSpecification sourceSpec, String packagePath)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public RevisionAccessContext getEntityRevisionContext(String projectId, SourceSpecification sourceSpec, String entityPath)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public RevisionStatus getRevisionStatus(String projectId, String revisionId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ private static class ProjectFileRevisionAccessContextWrapper implements RevisionAccessContext
+ {
+ private final ProjectFileAccessProvider.RevisionAccessContext revisionAccessContext;
+ private final Function super LegendSDLCServerException, ? extends LegendSDLCServerException> exceptionProcessor;
+
+ private ProjectFileRevisionAccessContextWrapper(ProjectFileAccessProvider.RevisionAccessContext revisionAccessContext, Function super LegendSDLCServerException, ? extends LegendSDLCServerException> exceptionProcessor)
+ {
+ this.revisionAccessContext = revisionAccessContext;
+ this.exceptionProcessor = exceptionProcessor;
+ }
+
+ private ProjectFileRevisionAccessContextWrapper(ProjectFileAccessProvider.RevisionAccessContext revisionAccessContext)
+ {
+ this(revisionAccessContext, null);
+ }
+
+ @Override
+ public Revision getRevision(String revisionId)
+ {
+ try
+ {
+ return this.revisionAccessContext.getRevision(revisionId);
+ }
+ catch (LegendSDLCServerException e)
+ {
+ if (this.exceptionProcessor != null)
+ {
+ LegendSDLCServerException processedException = this.exceptionProcessor.apply(e);
+ if (processedException != null)
+ {
+ throw processedException;
+ }
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public Revision getBaseRevision()
+ {
+ try
+ {
+ return this.revisionAccessContext.getBaseRevision();
+ }
+ catch (LegendSDLCServerException e)
+ {
+ if (this.exceptionProcessor != null)
+ {
+ LegendSDLCServerException processedException = this.exceptionProcessor.apply(e);
+ if (processedException != null)
+ {
+ throw processedException;
+ }
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public Revision getCurrentRevision()
+ {
+ try
+ {
+ return this.revisionAccessContext.getCurrentRevision();
+ }
+ catch (LegendSDLCServerException e)
+ {
+ if (this.exceptionProcessor != null)
+ {
+ LegendSDLCServerException processedException = this.exceptionProcessor.apply(e);
+ if (processedException != null)
+ {
+ throw processedException;
+ }
+ }
+ throw e;
+ }
+ }
+
+ @Override
+ public List getRevisions(Predicate super Revision> predicate, Instant since, Instant until, Integer limit)
+ {
+ try
+ {
+ return this.revisionAccessContext.getAllRevisions(predicate, since, until, limit).collect(Collectors.toList());
+ }
+ catch (LegendSDLCServerException e)
+ {
+ if (this.exceptionProcessor != null)
+ {
+ LegendSDLCServerException processedException = this.exceptionProcessor.apply(e);
+ if (processedException != null)
+ {
+ throw processedException;
+ }
+ }
+ throw e;
+ }
+ }
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/user/FileSystemUserApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/user/FileSystemUserApi.java
new file mode 100644
index 0000000000..b76cb7afbb
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/user/FileSystemUserApi.java
@@ -0,0 +1,61 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.user;
+
+import org.eclipse.collections.api.factory.Lists;
+import org.eclipse.collections.api.factory.Maps;
+import org.eclipse.collections.api.map.ImmutableMap;
+import org.finos.legend.sdlc.domain.model.user.User;
+import org.finos.legend.sdlc.server.domain.api.user.UserApi;
+import org.finos.legend.sdlc.server.domain.model.user.FileSystemUser;
+
+import javax.inject.Inject;
+import java.util.List;
+
+public class FileSystemUserApi implements UserApi
+{
+ public static final FileSystemUser LOCAL_USER = new FileSystemUser("local_user", "local_user");
+
+ private static final ImmutableMap users = Maps.immutable.of(LOCAL_USER.getUserId(), LOCAL_USER);
+
+ @Inject
+ public FileSystemUserApi()
+ {
+ }
+
+ @Override
+ public List getUsers()
+ {
+ return Lists.mutable.withAll(users.valuesView());
+ }
+
+ @Override
+ public User getUserById(String userId)
+ {
+ return users.get(userId);
+ }
+
+ @Override
+ public List findUsers(String searchString)
+ {
+ return Lists.mutable.withAll(users.select(user -> user.getUserId().contains(searchString) || user.getName().contains(searchString)));
+ }
+
+ @Override
+ public User getCurrentUserInfo()
+ {
+ return LOCAL_USER;
+ }
+}
\ No newline at end of file
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/version/FileSystemVersionApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/version/FileSystemVersionApi.java
new file mode 100644
index 0000000000..cd1760f54b
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/version/FileSystemVersionApi.java
@@ -0,0 +1,58 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.version;
+
+import org.finos.legend.sdlc.domain.model.version.Version;
+import org.finos.legend.sdlc.server.domain.api.version.NewVersionType;
+import org.finos.legend.sdlc.server.domain.api.version.VersionApi;
+import org.finos.legend.sdlc.server.exception.FSException;
+
+import javax.inject.Inject;
+import java.util.Collections;
+import java.util.List;
+
+public class FileSystemVersionApi implements VersionApi
+{
+
+ @Inject
+ public FileSystemVersionApi()
+ {
+ }
+
+ @Override
+ public List getVersions(String projectId, Integer minMajorVersion, Integer maxMajorVersion, Integer minMinorVersion, Integer maxMinorVersion, Integer minPatchVersion, Integer maxPatchVersion)
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public Version getLatestVersion(String projectId, Integer minMajorVersion, Integer maxMajorVersion, Integer minMinorVersion, Integer maxMinorVersion, Integer minPatchVersion, Integer maxPatchVersion)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Version getVersion(String projectId, int majorVersion, int minorVersion, int patchVersion)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Version newVersion(String projectId, NewVersionType type, String revisionId, String notes)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowApi.java
new file mode 100644
index 0000000000..b44ae72cca
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowApi.java
@@ -0,0 +1,42 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.workflow;
+
+import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification;
+import org.finos.legend.sdlc.server.exception.FSException;
+import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowAccessContext;
+import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowApi;
+
+import javax.inject.Inject;
+
+public class FileSystemWorkflowApi implements WorkflowApi
+{
+ @Inject
+ public FileSystemWorkflowApi()
+ {
+ }
+
+ @Override
+ public WorkflowAccessContext getWorkflowAccessContext(String projectId, SourceSpecification sourceSpecification)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public WorkflowAccessContext getReviewWorkflowAccessContext(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowJobApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowJobApi.java
new file mode 100644
index 0000000000..9475c54023
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workflow/FileSystemWorkflowJobApi.java
@@ -0,0 +1,42 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.workflow;
+
+import org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification;
+import org.finos.legend.sdlc.server.exception.FSException;
+import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowJobAccessContext;
+import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowJobApi;
+
+import javax.inject.Inject;
+
+public class FileSystemWorkflowJobApi implements WorkflowJobApi
+{
+ @Inject
+ public FileSystemWorkflowJobApi()
+ {
+ }
+
+ @Override
+ public WorkflowJobAccessContext getWorkflowJobAccessContext(String projectId, SourceSpecification sourceSpecification)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public WorkflowJobAccessContext getReviewWorkflowJobAccessContext(String projectId, String reviewId)
+ {
+ throw FSException.unavailableFeature();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workspace/FileSystemWorkspaceApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workspace/FileSystemWorkspaceApi.java
new file mode 100644
index 0000000000..da6353fcf9
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/api/workspace/FileSystemWorkspaceApi.java
@@ -0,0 +1,342 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.api.workspace;
+
+import org.eclipse.collections.api.factory.Lists;
+import org.eclipse.collections.api.list.MutableList;
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ListBranchCommand;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.finos.legend.sdlc.domain.model.project.workspace.Workspace;
+import org.finos.legend.sdlc.domain.model.project.workspace.WorkspaceType;
+import org.finos.legend.sdlc.server.api.entity.FileSystemApiWithFileAccess;
+import org.finos.legend.sdlc.server.api.user.FileSystemUserApi;
+import org.finos.legend.sdlc.server.domain.api.workspace.PatchWorkspaceSource;
+import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceApi;
+import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSource;
+import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceSpecification;
+import org.finos.legend.sdlc.server.error.LegendSDLCServerException;
+import org.finos.legend.sdlc.server.exception.FSException;
+import org.finos.legend.sdlc.server.project.ProjectFileAccessProvider;
+import org.finos.legend.sdlc.server.startup.FSConfiguration;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.*;
+import javax.inject.Inject;
+import javax.ws.rs.core.Response;
+
+import static org.finos.legend.sdlc.server.domain.api.project.source.SourceSpecification.workspaceSourceSpecification;
+
+public class FileSystemWorkspaceApi extends FileSystemApiWithFileAccess implements WorkspaceApi
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(FileSystemWorkspaceApi.class);
+
+ private static final String WORKSPACE_BRANCH_PREFIX = "workspace";
+ private static final String CONFLICT_RESOLUTION_BRANCH_PREFIX = "resolution";
+ private static final String BACKUP_BRANCH_PREFIX = "backup";
+ private static final String GROUP_WORKSPACE_BRANCH_PREFIX = "group";
+ private static final String GROUP_CONFLICT_RESOLUTION_BRANCH_PREFIX = "group-resolution";
+ private static final String GROUP_BACKUP_BRANCH_PREFIX = "group-backup";
+ private static final String PATCH_WORKSPACE_BRANCH_PREFIX = "patch";
+
+ protected static final char BRANCH_DELIMITER = '/';
+
+ @Inject
+ public FileSystemWorkspaceApi(FSConfiguration fsConfiguration)
+ {
+ super(fsConfiguration);
+ }
+
+ @Override
+ public Workspace getWorkspace(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ String refBranchName = Constants.R_HEADS + getRefBranchName(workspaceSourceSpecification(workspaceSpecification));
+ Ref branch = getGitBranch(projectId, refBranchName);
+ return workspaceBranchToWorkspace(projectId, branch, workspaceSpecification.getType());
+ }
+
+ @Override
+ public List getWorkspaces(String projectId, Set types, Set accessTypes, Set sources)
+ {
+ if (sources == null)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ Set resolvedTypes = (types == null) ? EnumSet.allOf(WorkspaceType.class) : types;
+ Set resolvedAccessTypes = (accessTypes == null) ? EnumSet.allOf(ProjectFileAccessProvider.WorkspaceAccessType.class) : accessTypes;
+
+ Repository repository = retrieveRepo(projectId);
+ MutableList result = Lists.mutable.empty();
+ // currently only WORKSPACE access type is supported
+ if (resolvedAccessTypes.contains(ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE))
+ {
+ if (resolvedTypes.contains(WorkspaceType.GROUP))
+ {
+ Collection extends Workspace> UserWS = getBranchesByType(repository, "user", projectId, WorkspaceType.USER);
+ result.addAllIterable(UserWS);
+ }
+ if (resolvedTypes.contains(WorkspaceType.USER))
+ {
+ Collection extends Workspace> GroupWS = getBranchesByType(repository, "group", projectId, WorkspaceType.GROUP);
+ result.addAllIterable(GroupWS);
+ }
+ }
+ return result;
+ }
+
+ @Override
+ public List getAllWorkspaces(String projectId, Set types, Set accessTypes, Set sources)
+ {
+ return this.getWorkspaces(projectId, types, accessTypes, sources);
+ }
+
+ @Override
+ public Workspace newWorkspace(String projectId, String workspaceId, WorkspaceType type, WorkspaceSource source)
+ {
+ LegendSDLCServerException.validateNonNull(projectId, "projectId may not be null");
+ LegendSDLCServerException.validateNonNull(workspaceId, "workspaceId may not be null");
+ LegendSDLCServerException.validateNonNull(type, "workspace type may not be null");
+ LegendSDLCServerException.validateNonNull(source, "workspace source may not be null");
+ validateWorkspaceId(workspaceId);
+
+ WorkspaceSpecification workspaceSpecification = WorkspaceSpecification.newWorkspaceSpecification(workspaceId, type, ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE, source);
+ String workspaceBranchName = getWorkspaceBranchName(workspaceSpecification);
+
+ Ref branchRef;
+ try
+ {
+ Repository repository = retrieveRepo(projectId);
+ Git git = new Git(repository);
+ git.checkout().setName("master").call();
+ branchRef = git.branchCreate().setName(workspaceBranchName).call();
+ git.getRepository().getConfig().setString("branch", workspaceBranchName, "type", type.getLabel());
+ git.getRepository().getConfig().save();
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Failed to create workspace " + workspaceBranchName + " for project " + projectId, e);
+ }
+ return workspaceBranchToWorkspace(projectId, branchRef, workspaceSpecification.getType());
+ }
+
+ private static void validateWorkspaceId(String idString)
+ {
+ validateWorkspaceId(idString, null);
+ }
+
+ private static void validateWorkspaceId(String idString, Response.Status errorStatus)
+ {
+ if (!isValidWorkspaceId(idString))
+ {
+ throw new LegendSDLCServerException("Invalid workspace id: \"" + idString + "\". A workspace id must be a non-empty string consisting of characters from the following set: {a-z, A-Z, 0-9, _, ., -}. The id may not contain \"..\" and may not start or end with '.' or '-'.", (errorStatus == null) ? Response.Status.BAD_REQUEST : errorStatus);
+ }
+ }
+
+ private static boolean isValidWorkspaceId(String string)
+ {
+ if ((string == null) || string.isEmpty())
+ {
+ return false;
+ }
+
+ if (!isValidWorkspaceStartEndChar(string.charAt(0)))
+ {
+ return false;
+ }
+ int lastIndex = string.length() - 1;
+ for (int i = 1; i < lastIndex; i++)
+ {
+ char c = string.charAt(i);
+ boolean isValid = isValidWorkspaceStartEndChar(c) || (c == '-') || ((c == '.') && (string.charAt(i - 1) != '.'));
+ if (!isValid)
+ {
+ return false;
+ }
+ }
+ return isValidWorkspaceStartEndChar(string.charAt(lastIndex));
+ }
+
+ private static boolean isValidWorkspaceStartEndChar(char c)
+ {
+ return (c == '_') || (('a' <= c) && (c <= 'z')) || (('A' <= c) && (c <= 'Z')) || (('0' <= c) && (c <= '9'));
+ }
+
+ public static String getWorkspaceBranchName(WorkspaceSpecification workspaceSpec)
+ {
+ String userId = (workspaceSpec.getType() == WorkspaceType.GROUP) ? null : FileSystemUserApi.LOCAL_USER.getUserId();
+ return getWorkspaceBranchName(workspaceSpec, userId);
+ }
+
+ protected static String getWorkspaceBranchName(WorkspaceSpecification workspaceSpec, String currentUser)
+ {
+ StringBuilder builder = new StringBuilder();
+ WorkspaceSource source = workspaceSpec.getSource();
+ if (source instanceof PatchWorkspaceSource)
+ {
+ builder.append(PATCH_WORKSPACE_BRANCH_PREFIX).append(BRANCH_DELIMITER);
+ ((PatchWorkspaceSource) source).getPatchVersionId().appendVersionIdString(builder).append(BRANCH_DELIMITER);
+ }
+ builder.append(getWorkspaceBranchNamePrefix(workspaceSpec.getType(), workspaceSpec.getAccessType())).append(BRANCH_DELIMITER);
+ if (workspaceSpec.getType() == WorkspaceType.USER)
+ {
+ String userId = workspaceSpec.getUserId();
+ builder.append((userId == null) ? currentUser : userId).append(BRANCH_DELIMITER);
+ }
+ return builder.append(workspaceSpec.getId()).toString();
+ }
+
+ protected static String getWorkspaceBranchNamePrefix(WorkspaceType workspaceType, ProjectFileAccessProvider.WorkspaceAccessType workspaceAccessType)
+ {
+ assert workspaceAccessType != null : "Workspace access type has been used but it is null, which should not be the case";
+
+ switch (workspaceType)
+ {
+ case GROUP:
+ {
+ switch (workspaceAccessType)
+ {
+ case WORKSPACE:
+ {
+ return GROUP_WORKSPACE_BRANCH_PREFIX;
+ }
+ case CONFLICT_RESOLUTION:
+ {
+ return GROUP_CONFLICT_RESOLUTION_BRANCH_PREFIX;
+ }
+ case BACKUP:
+ {
+ return GROUP_BACKUP_BRANCH_PREFIX;
+ }
+ default:
+ {
+ throw new RuntimeException("Unknown workspace access type: " + workspaceAccessType);
+ }
+ }
+ }
+ case USER:
+ {
+ switch (workspaceAccessType)
+ {
+ case WORKSPACE:
+ {
+ return WORKSPACE_BRANCH_PREFIX;
+ }
+ case CONFLICT_RESOLUTION:
+ {
+ return CONFLICT_RESOLUTION_BRANCH_PREFIX;
+ }
+ case BACKUP:
+ {
+ return BACKUP_BRANCH_PREFIX;
+ }
+ default:
+ {
+ throw new RuntimeException("Unknown workspace access type: " + workspaceAccessType);
+ }
+ }
+ }
+ default:
+ {
+ throw new RuntimeException("Unknown workspace type: " + workspaceType);
+ }
+ }
+ }
+
+ @Override
+ public void deleteWorkspace(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @Override
+ public boolean isWorkspaceOutdated(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ return false;
+ }
+
+ @Override
+ public boolean isWorkspaceInConflictResolutionMode(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ return false;
+ }
+
+ @Override
+ public WorkspaceUpdateReport updateWorkspace(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ private Collection extends Workspace> getBranchesByType(Repository repository, String branchType, String projectId, WorkspaceType wType)
+ {
+ List branchesOfType = new ArrayList<>();
+ try
+ {
+ List[ allBranches = Git.wrap(repository).branchList().setListMode(ListBranchCommand.ListMode.ALL).call();
+ for (Ref branch : allBranches)
+ {
+ String type = repository.getConfig().getString("branch", Repository.shortenRefName(branch.getName()), "type");
+ if (type != null && type.equals(branchType))
+ {
+ branchesOfType.add(workspaceBranchToWorkspace(projectId, branch, wType));
+ }
+ }
+ }
+ catch (Exception e)
+ {
+ LOGGER.error("Error occurred during branch list operation for project {}", projectId, e);
+ throw FSException.getLegendSDLCServerException("Failed to fetch " + branchType + "workspaces for project " + projectId, e);
+ }
+ return branchesOfType;
+ }
+
+ private static Workspace workspaceBranchToWorkspace(String projectId, Ref branch, WorkspaceType workspaceType)
+ {
+ String workspaceIdWithType = Repository.shortenRefName(branch.getName());
+ String workspaceId = workspaceIdWithType.substring(workspaceIdWithType.lastIndexOf('/') + 1);
+
+ WorkspaceSpecification workspaceSpecification = WorkspaceSpecification.newWorkspaceSpecification(workspaceId, workspaceType, ProjectFileAccessProvider.WorkspaceAccessType.WORKSPACE, null);
+ return (branch == null) ? null : fromWorkspaceBranchName(projectId, workspaceSpecification);
+ }
+
+ protected static Workspace fromWorkspaceBranchName(String projectId, WorkspaceSpecification workspaceSpecification)
+ {
+ String userId = workspaceSpecification.getType() == WorkspaceType.GROUP ? null : FileSystemUserApi.LOCAL_USER.getUserId();
+ String workspaceId = workspaceSpecification.getId();
+ return new Workspace()
+ {
+ @Override
+ public String getProjectId()
+ {
+ return projectId;
+ }
+
+ @Override
+ public String getUserId()
+ {
+ return userId;
+ }
+
+ @Override
+ public String getWorkspaceId()
+ {
+ return workspaceId;
+ }
+ };
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/depot/FileSystemMetadataApi.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/depot/FileSystemMetadataApi.java
new file mode 100644
index 0000000000..b8c5a0dd51
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/depot/FileSystemMetadataApi.java
@@ -0,0 +1,45 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.depot;
+
+import org.finos.legend.sdlc.domain.model.entity.Entity;
+import org.finos.legend.sdlc.server.depot.api.MetadataApi;
+import org.finos.legend.sdlc.server.depot.model.DepotProjectId;
+import org.finos.legend.sdlc.server.depot.model.DepotProjectVersion;
+import org.finos.legend.sdlc.server.exception.FSException;
+
+import java.util.List;
+import java.util.Set;
+import javax.inject.Inject;
+
+public class FileSystemMetadataApi implements MetadataApi
+{
+ @Inject
+ public FileSystemMetadataApi()
+ {
+ }
+
+ @Override
+ public List getEntities(DepotProjectId projectId, String versionId)
+ {
+ throw FSException.unavailableFeature();
+ }
+
+ @Override
+ public Set getProjectDependencies(DepotProjectId projectId, String versionId, boolean transitive)
+ {
+ throw FSException.unavailableFeature();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/project/configuration/FileSystemProjectConfiguration.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/project/configuration/FileSystemProjectConfiguration.java
new file mode 100644
index 0000000000..7e183eb8c4
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/project/configuration/FileSystemProjectConfiguration.java
@@ -0,0 +1,75 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.domain.model.project.configuration;
+
+import org.finos.legend.sdlc.domain.model.project.configuration.MetamodelDependency;
+import org.finos.legend.sdlc.domain.model.project.configuration.ProjectConfiguration;
+import org.finos.legend.sdlc.domain.model.project.configuration.ProjectDependency;
+import org.finos.legend.sdlc.domain.model.project.configuration.ProjectStructureVersion;
+
+import java.util.Collections;
+import java.util.List;
+
+public class FileSystemProjectConfiguration implements ProjectConfiguration
+{
+ private final String projectId;
+ private final ProjectStructureVersion projectStructureVersion;
+ private final String groupId;
+ private final String artifactId;
+
+ public FileSystemProjectConfiguration(String projectId, ProjectStructureVersion projectStructureVersion, String groupId, String artifactId)
+ {
+ this.projectId = projectId;
+ this.projectStructureVersion = projectStructureVersion;
+ this.groupId = groupId;
+ this.artifactId = artifactId;
+ }
+
+ @Override
+ public String getProjectId()
+ {
+ return this.projectId;
+ }
+
+ @Override
+ public ProjectStructureVersion getProjectStructureVersion()
+ {
+ return this.projectStructureVersion;
+ }
+
+ @Override
+ public String getGroupId()
+ {
+ return this.groupId;
+ }
+
+ @Override
+ public String getArtifactId()
+ {
+ return this.artifactId;
+ }
+
+ @Override
+ public List getProjectDependencies()
+ {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List getMetamodelDependencies()
+ {
+ return Collections.emptyList();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/revision/FileSystemRevision.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/revision/FileSystemRevision.java
new file mode 100644
index 0000000000..75dac817d0
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/revision/FileSystemRevision.java
@@ -0,0 +1,101 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.domain.model.revision;
+
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.finos.legend.sdlc.domain.model.revision.Revision;
+import org.finos.legend.sdlc.server.exception.FSException;
+
+import java.time.Instant;
+
+public class FileSystemRevision implements Revision
+{
+ private final String id;
+ private final String authorName;
+ private final Instant authoredTimestamp;
+ private final String committerName;
+ private final Instant committedTimestamp;
+ private final String message;
+
+ public FileSystemRevision(String revisionId, String authorName, Instant authoredTimeStamp, String committerName, Instant committedTimeStamp, String message)
+ {
+ this.id = revisionId;
+ this.authorName = authorName;
+ this.authoredTimestamp = authoredTimeStamp;
+ this.committerName = committerName;
+ this.committedTimestamp = committedTimeStamp;
+ this.message = message;
+ }
+
+ public static FileSystemRevision getFileSystemRevision(String projectId, String workspaceId, Repository repo, Ref branchRef)
+ {
+ try
+ {
+ RevWalk revWalk = new RevWalk(repo);
+ RevCommit commit = revWalk.parseCommit(branchRef.getObjectId());
+ revWalk.dispose();
+ String revisionId = commit.getId().getName();
+ String authorName = commit.getAuthorIdent().getName();
+ Instant authoredTimeStamp = commit.getAuthorIdent().getWhenAsInstant();
+ String committerName = commit.getCommitterIdent().getName();
+ Instant committedTimeStamp = commit.getCommitterIdent().getWhenAsInstant();
+ String message = commit.getFullMessage();
+ return new FileSystemRevision(revisionId, authorName, authoredTimeStamp, committerName, committedTimeStamp, message);
+ }
+ catch (Exception e)
+ {
+ throw FSException.getLegendSDLCServerException("Failed to get revision for workspace " + workspaceId + " in project " + projectId, e);
+ }
+ }
+
+ @Override
+ public String getId()
+ {
+ return id;
+ }
+
+ @Override
+ public String getAuthorName()
+ {
+ return authorName;
+ }
+
+ @Override
+ public Instant getAuthoredTimestamp()
+ {
+ return authoredTimestamp;
+ }
+
+ @Override
+ public String getCommitterName()
+ {
+ return committerName;
+ }
+
+ @Override
+ public Instant getCommittedTimestamp()
+ {
+ return committedTimestamp;
+ }
+
+ @Override
+ public String getMessage()
+ {
+ return message;
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/user/FileSystemUser.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/user/FileSystemUser.java
new file mode 100644
index 0000000000..4727af9fd1
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/domain/model/user/FileSystemUser.java
@@ -0,0 +1,42 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.domain.model.user;
+
+import org.finos.legend.sdlc.domain.model.user.User;
+
+public class FileSystemUser implements User
+{
+ private final String userId;
+ private final String name;
+
+ public FileSystemUser(String userId, String name)
+ {
+ this.userId = userId;
+ this.name = name;
+ }
+
+ @Override
+ public String getUserId()
+ {
+ return userId;
+ }
+
+ @Override
+ public String getName()
+ {
+ return name;
+ }
+
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/exception/FSException.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/exception/FSException.java
new file mode 100644
index 0000000000..571164dcb4
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/exception/FSException.java
@@ -0,0 +1,31 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.exception;
+
+import org.finos.legend.sdlc.server.error.LegendSDLCServerException;
+
+public class FSException
+{
+ public static UnsupportedOperationException unavailableFeature()
+ {
+ return new UnsupportedOperationException("Feature unavailable");
+ }
+
+ public static LegendSDLCServerException getLegendSDLCServerException(String errorMessage, Exception e)
+ {
+ String exceptionMessage = e.getMessage();
+ return new LegendSDLCServerException(exceptionMessage != null ? errorMessage + " : " + exceptionMessage : errorMessage, e);
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthCheckResource.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthCheckResource.java
new file mode 100644
index 0000000000..06ec4c57b2
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthCheckResource.java
@@ -0,0 +1,47 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.resources;
+
+import javax.inject.Inject;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+@Path("/auth")
+public class FileSystemAuthCheckResource extends BaseResource
+{
+ private final HttpServletRequest httpRequest;
+ private final HttpServletResponse httpResponse;
+
+ @Inject
+ public FileSystemAuthCheckResource(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
+ {
+ super();
+ this.httpRequest = httpRequest;
+ this.httpResponse = httpResponse;
+ }
+
+ @GET
+ @Path("authorized")
+ @Produces(MediaType.APPLICATION_JSON)
+ public boolean isAuthorized()
+ {
+ return true;
+ }
+
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthResource.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthResource.java
new file mode 100644
index 0000000000..d0188a78a9
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/resources/FileSystemAuthResource.java
@@ -0,0 +1,66 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.resources;
+
+import io.swagger.annotations.ApiParam;
+
+import javax.inject.Inject;
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
+import java.util.Collections;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+@Path("/auth")
+public class FileSystemAuthResource extends BaseResource
+{
+ private static final Pattern TERMS_OF_SERVICE_MESSAGE_PATTERN = Pattern.compile("terms\\s++of\\s++service", Pattern.CASE_INSENSITIVE);
+
+ @Inject
+ public FileSystemAuthResource()
+ {
+ super();
+ }
+
+ @GET
+ @Path("callback")
+ public Object callback(@QueryParam("code") String code, @QueryParam("state") String state)
+ {
+ throw new UnsupportedOperationException("Not implemented");
+ }
+
+ @GET
+ @Path("authorize")
+ @Produces(MediaType.TEXT_HTML)
+ public String authorize(@QueryParam("redirect_uri") @ApiParam("URI to redirect to when authorization is complete") String redirectUri)
+ {
+ return executeWithLogging("authorizing", () ->
+ {
+ return "]Success
";
+ });
+ }
+
+ @GET
+ @Path("termsOfServiceAcceptance")
+ @Produces(MediaType.APPLICATION_JSON)
+ // NOTE: we have to return a set for backward compatibility reason
+ public Set termsOfServiceAcceptance()
+ {
+ return Collections.emptySet();
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSConfiguration.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSConfiguration.java
new file mode 100644
index 0000000000..ccedaf26c4
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSConfiguration.java
@@ -0,0 +1,39 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.startup;
+
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class FSConfiguration
+{
+ public final String rootDirectory;
+
+ private FSConfiguration(String rootDirectory)
+ {
+ this.rootDirectory = rootDirectory;
+ }
+
+ public String getRootDirectory()
+ {
+ return rootDirectory;
+ }
+
+ @JsonCreator
+ public static FSConfiguration newConfiguration(@JsonProperty("rootDirectory") String rootDirectory)
+ {
+ return new FSConfiguration(rootDirectory);
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSModule.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSModule.java
new file mode 100644
index 0000000000..fd0d124ec6
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/FSModule.java
@@ -0,0 +1,546 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.startup;
+
+import com.google.inject.Binder;
+import com.hubspot.dropwizard.guicier.DropwizardAwareModule;
+import org.finos.legend.sdlc.server.BaseServer;
+import org.finos.legend.sdlc.server.api.backup.FileSystemBackupApi;
+import org.finos.legend.sdlc.server.api.build.FileSystemBuildApi;
+import org.finos.legend.sdlc.server.api.comparison.FileSystemComparisonApi;
+import org.finos.legend.sdlc.server.api.conflictresolution.FileSystemConflictResolutionApi;
+import org.finos.legend.sdlc.server.api.entity.FileSystemEntityApi;
+import org.finos.legend.sdlc.server.api.issue.FileSystemIssueApi;
+import org.finos.legend.sdlc.server.api.patch.FileSystemPatchApi;
+import org.finos.legend.sdlc.server.api.project.FileSystemProjectApi;
+import org.finos.legend.sdlc.server.api.project.FileSystemProjectConfigurationApi;
+import org.finos.legend.sdlc.server.api.review.FileSystemReviewApi;
+import org.finos.legend.sdlc.server.api.revision.FileSystemRevisionApi;
+import org.finos.legend.sdlc.server.api.user.FileSystemUserApi;
+import org.finos.legend.sdlc.server.api.version.FileSystemVersionApi;
+import org.finos.legend.sdlc.server.api.workflow.FileSystemWorkflowApi;
+import org.finos.legend.sdlc.server.api.workflow.FileSystemWorkflowJobApi;
+import org.finos.legend.sdlc.server.api.workspace.FileSystemWorkspaceApi;
+import org.finos.legend.sdlc.server.config.LegendSDLCServerFeaturesConfiguration;
+import org.finos.legend.sdlc.server.depot.DepotConfiguration;
+import org.finos.legend.sdlc.server.depot.FileSystemMetadataApi;
+import org.finos.legend.sdlc.server.depot.api.MetadataApi;
+import org.finos.legend.sdlc.server.depot.auth.AuthClientInjector;
+import org.finos.legend.sdlc.server.domain.api.backup.BackupApi;
+import org.finos.legend.sdlc.server.domain.api.build.BuildApi;
+import org.finos.legend.sdlc.server.domain.api.comparison.ComparisonApi;
+import org.finos.legend.sdlc.server.domain.api.conflictResolution.ConflictResolutionApi;
+import org.finos.legend.sdlc.server.domain.api.dependency.DependenciesApi;
+import org.finos.legend.sdlc.server.domain.api.dependency.DependenciesApiImpl;
+import org.finos.legend.sdlc.server.domain.api.entity.EntityApi;
+import org.finos.legend.sdlc.server.domain.api.issue.IssueApi;
+import org.finos.legend.sdlc.server.domain.api.patch.PatchApi;
+import org.finos.legend.sdlc.server.domain.api.project.ProjectApi;
+import org.finos.legend.sdlc.server.domain.api.project.ProjectConfigurationApi;
+import org.finos.legend.sdlc.server.domain.api.review.ReviewApi;
+import org.finos.legend.sdlc.server.domain.api.revision.RevisionApi;
+import org.finos.legend.sdlc.server.domain.api.test.TestModelBuilder;
+import org.finos.legend.sdlc.server.domain.api.user.UserApi;
+import org.finos.legend.sdlc.server.domain.api.version.VersionApi;
+import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowApi;
+import org.finos.legend.sdlc.server.domain.api.workflow.WorkflowJobApi;
+import org.finos.legend.sdlc.server.domain.api.workspace.WorkspaceApi;
+import org.finos.legend.sdlc.server.guice.UserContext;
+import org.finos.legend.sdlc.server.project.ProjectStructurePlatformExtensions;
+import org.finos.legend.sdlc.server.project.config.ProjectPlatformsConfiguration;
+import org.finos.legend.sdlc.server.project.config.ProjectStructureConfiguration;
+import org.finos.legend.sdlc.server.project.extension.DefaultProjectStructureExtensionProvider;
+import org.finos.legend.sdlc.server.project.extension.ProjectStructureExtension;
+import org.finos.legend.sdlc.server.project.extension.ProjectStructureExtensionProvider;
+import org.finos.legend.sdlc.server.project.extension.VoidProjectStructureExtensionProvider;
+import org.finos.legend.sdlc.server.resources.FileSystemAuthCheckResource;
+import org.finos.legend.sdlc.server.resources.FileSystemAuthResource;
+import org.finos.legend.sdlc.server.resources.InfoResource;
+import org.finos.legend.sdlc.server.resources.ServerResource;
+import org.finos.legend.sdlc.server.resources.backup.patch.group.*;
+import org.finos.legend.sdlc.server.resources.backup.patch.user.*;
+import org.finos.legend.sdlc.server.resources.backup.project.BackupProjectResource;
+import org.finos.legend.sdlc.server.resources.backup.project.group.*;
+import org.finos.legend.sdlc.server.resources.backup.project.user.*;
+import org.finos.legend.sdlc.server.resources.build.ProjectBuildsResource;
+import org.finos.legend.sdlc.server.resources.build.VersionBuildsResource;
+import org.finos.legend.sdlc.server.resources.build.WorkspaceBuildsResource;
+import org.finos.legend.sdlc.server.resources.comparison.patch.ComparisonPatchReviewEntitiesResource;
+import org.finos.legend.sdlc.server.resources.comparison.patch.ComparisonPatchReviewProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.comparison.patch.ComparisonPatchReviewResource;
+import org.finos.legend.sdlc.server.resources.comparison.patch.group.PatchesGroupComparisonWorkspaceResource;
+import org.finos.legend.sdlc.server.resources.comparison.patch.user.ComparisonPatchesWorkspaceResource;
+import org.finos.legend.sdlc.server.resources.comparison.project.ComparisonReviewEntitiesResource;
+import org.finos.legend.sdlc.server.resources.comparison.project.ComparisonReviewProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.comparison.project.ComparisonReviewResource;
+import org.finos.legend.sdlc.server.resources.comparison.project.group.GroupComparisonWorkspaceResource;
+import org.finos.legend.sdlc.server.resources.comparison.project.user.ComparisonWorkspaceResource;
+import org.finos.legend.sdlc.server.resources.conflictResolution.patch.ConflictResolutionPatchResource;
+import org.finos.legend.sdlc.server.resources.conflictResolution.patch.group.*;
+import org.finos.legend.sdlc.server.resources.conflictResolution.patch.user.*;
+import org.finos.legend.sdlc.server.resources.conflictResolution.project.ConflictResolutionProjectResource;
+import org.finos.legend.sdlc.server.resources.conflictResolution.project.group.*;
+import org.finos.legend.sdlc.server.resources.conflictResolution.project.user.*;
+import org.finos.legend.sdlc.server.resources.dependency.patch.PatchRevisionDependenciesResource;
+import org.finos.legend.sdlc.server.resources.dependency.patch.group.PatchesGroupWorkspaceRevisionDependenciesResource;
+import org.finos.legend.sdlc.server.resources.dependency.patch.user.PatchesWorkspaceRevisionDependenciesResource;
+import org.finos.legend.sdlc.server.resources.dependency.project.DownstreamDependenciesResource;
+import org.finos.legend.sdlc.server.resources.dependency.project.ProjectRevisionDependenciesResource;
+import org.finos.legend.sdlc.server.resources.dependency.project.ProjectVersionDependenciesResource;
+import org.finos.legend.sdlc.server.resources.dependency.project.group.GroupWorkspaceRevisionDependenciesResource;
+import org.finos.legend.sdlc.server.resources.dependency.project.user.WorkspaceRevisionDependenciesResource;
+import org.finos.legend.sdlc.server.resources.entity.VersionEntitiesResource;
+import org.finos.legend.sdlc.server.resources.entity.VersionEntityPathsResource;
+import org.finos.legend.sdlc.server.resources.entity.patch.PatchesProjectEntitiesResource;
+import org.finos.legend.sdlc.server.resources.entity.patch.PatchesProjectEntityPathsResource;
+import org.finos.legend.sdlc.server.resources.entity.patch.group.*;
+import org.finos.legend.sdlc.server.resources.entity.patch.user.*;
+import org.finos.legend.sdlc.server.resources.entity.project.ProjectEntitiesResource;
+import org.finos.legend.sdlc.server.resources.entity.project.ProjectEntityPathsResource;
+import org.finos.legend.sdlc.server.resources.entity.project.ProjectRevisionEntitiesResource;
+import org.finos.legend.sdlc.server.resources.entity.project.ProjectRevisionEntityPathsResource;
+import org.finos.legend.sdlc.server.resources.entity.project.group.*;
+import org.finos.legend.sdlc.server.resources.entity.project.user.*;
+import org.finos.legend.sdlc.server.resources.issue.IssuesResource;
+import org.finos.legend.sdlc.server.resources.patch.PatchesResource;
+import org.finos.legend.sdlc.server.resources.pmcd.VersionPureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.pmcd.patch.PatchPureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.pmcd.patch.PatchRevisionPureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.pmcd.patch.group.PatchesGroupWorkspacePureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.pmcd.patch.group.PatchesGroupWorkspaceRevisionPureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.pmcd.patch.user.PatchesWorkspacePureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.pmcd.project.ProjectPureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.pmcd.project.group.GroupWorkspacePureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.pmcd.project.group.GroupWorkspaceRevisionPureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.pmcd.project.user.WorkspacePureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.pmcd.project.user.WorkspaceRevisionPureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.project.ConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.VersionProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.patch.PatchProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.patch.group.PatchesGroupWorkspaceProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.patch.group.PatchesGroupWorkspaceRevisionProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.patch.user.PatchesWorkspaceProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.patch.user.PatchesWorkspaceRevisionProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.project.ProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.project.ProjectRevisionProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.project.ProjectRevisionPureModelContextDataResource;
+import org.finos.legend.sdlc.server.resources.project.project.ProjectsResource;
+import org.finos.legend.sdlc.server.resources.project.project.group.GroupWorkspaceProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.project.group.GroupWorkspaceRevisionProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.project.user.WorkspaceProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.project.project.user.WorkspaceRevisionProjectConfigurationResource;
+import org.finos.legend.sdlc.server.resources.review.ReviewsOnlyResource;
+import org.finos.legend.sdlc.server.resources.review.patch.PatchReviewsResource;
+import org.finos.legend.sdlc.server.resources.review.project.ReviewsResource;
+import org.finos.legend.sdlc.server.resources.revision.patch.PatchEntityRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.patch.PatchPackageRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.patch.PatchRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.patch.group.PatchesGroupWorkspaceEntityRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.patch.group.PatchesGroupWorkspacePackageRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.patch.group.PatchesGroupWorkspaceRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.patch.user.PatchesWorkspaceRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.project.ProjectEntityRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.project.ProjectPackageRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.project.ProjectRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.project.group.GroupWorkspaceEntityRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.project.group.GroupWorkspacePackageRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.project.group.GroupWorkspaceRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.project.user.WorkspaceEntityRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.project.user.WorkspacePackageRevisionsResource;
+import org.finos.legend.sdlc.server.resources.revision.project.user.WorkspaceRevisionsResource;
+import org.finos.legend.sdlc.server.resources.user.CurrentUserResource;
+import org.finos.legend.sdlc.server.resources.user.UsersResource;
+import org.finos.legend.sdlc.server.resources.version.VersionsResource;
+import org.finos.legend.sdlc.server.resources.workflow.ReviewWorkflowJobsResource;
+import org.finos.legend.sdlc.server.resources.workflow.ReviewWorkflowsResource;
+import org.finos.legend.sdlc.server.resources.workflow.VersionWorkflowJobsResource;
+import org.finos.legend.sdlc.server.resources.workflow.VersionWorkflowsResource;
+import org.finos.legend.sdlc.server.resources.workflow.patch.PatchReviewWorkflowJobsResource;
+import org.finos.legend.sdlc.server.resources.workflow.patch.PatchReviewWorkflowsResource;
+import org.finos.legend.sdlc.server.resources.workflow.patch.PatchWorkflowJobsResource;
+import org.finos.legend.sdlc.server.resources.workflow.patch.PatchWorkflowsResource;
+import org.finos.legend.sdlc.server.resources.workflow.patch.group.PatchesGroupWorkspaceWorkflowJobsResource;
+import org.finos.legend.sdlc.server.resources.workflow.patch.group.PatchesGroupWorkspaceWorkflowsResource;
+import org.finos.legend.sdlc.server.resources.workflow.patch.user.PatchesWorkspaceWorkflowJobsResource;
+import org.finos.legend.sdlc.server.resources.workflow.patch.user.PatchesWorkspaceWorkflowsResource;
+import org.finos.legend.sdlc.server.resources.workflow.project.ProjectWorkflowJobsResource;
+import org.finos.legend.sdlc.server.resources.workflow.project.ProjectWorkflowsResource;
+import org.finos.legend.sdlc.server.resources.workflow.project.group.GroupWorkspaceWorkflowJobsResource;
+import org.finos.legend.sdlc.server.resources.workflow.project.group.GroupWorkspaceWorkflowsResource;
+import org.finos.legend.sdlc.server.resources.workflow.project.user.WorkspaceWorkflowJobsResource;
+import org.finos.legend.sdlc.server.resources.workflow.project.user.WorkspaceWorkflowsResource;
+import org.finos.legend.sdlc.server.resources.workspace.patch.group.PatchesGroupWorkspacesResource;
+import org.finos.legend.sdlc.server.resources.workspace.patch.user.PatchesWorkspacesResource;
+import org.finos.legend.sdlc.server.resources.workspace.project.group.GroupWorkspacesResource;
+import org.finos.legend.sdlc.server.resources.workspace.project.user.WorkspacesResource;
+import org.finos.legend.sdlc.server.tools.BackgroundTaskProcessor;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+public class FSModule extends DropwizardAwareModule
+{
+ protected final LegendSDLCServerFS> server;
+ protected ProjectStructureExtensionProvider extensionProvider;
+ private AuthClientInjector authClientInjector;
+
+ public FSModule(LegendSDLCServerFS> server)
+ {
+ this.server = server;
+ }
+
+ @Override
+ public void configure(Binder binder)
+ {
+ configureCommonApis(binder);
+ configureApis(binder);
+ initRootDirectory(getFSConfiguration());
+
+ binder.bind(UserContext.class);
+ binder.bind(TestModelBuilder.class);
+ binder.bind(FSConfiguration.class).toProvider(this::getFSConfiguration);
+ binder.bind(ProjectStructureConfiguration.class).toProvider(this::getProjectStructureConfiguration);
+ binder.bind(ProjectStructureExtensionProvider.class).toProvider(this::getProjectStructureExtensionProvider);
+ binder.bind(DepotConfiguration.class).toProvider(this::getDepotConfiguration);
+ binder.bind(AuthClientInjector.class).toProvider(this::getAuthClientInjector);
+ binder.bind(BaseServer.ServerInfo.class).toProvider(this.server::getServerInfo);
+ binder.bind(LegendSDLCServerFeaturesConfiguration.class).toProvider(this::getFeaturesConfiguration);
+ binder.bind(BackgroundTaskProcessor.class).toProvider(this.server::getBackgroundTaskProcessor);
+ binder.bind(ProjectStructurePlatformExtensions.class).toInstance(buildProjectStructurePlatformExtensions());
+
+ bindResources(binder);
+ }
+
+ private void bindResources(Binder binder)
+ {
+ binder.bind(InfoResource.class);
+ binder.bind(ServerResource.class);
+ binder.bind(ProjectsResource.class);
+ binder.bind(WorkspacesResource.class);
+ binder.bind(ProjectConfigurationResource.class);
+ binder.bind(ConfigurationResource.class);
+ binder.bind(ProjectRevisionProjectConfigurationResource.class);
+ binder.bind(WorkspaceProjectConfigurationResource.class);
+ binder.bind(WorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(ProjectEntitiesResource.class);
+ binder.bind(ProjectPureModelContextDataResource.class);
+ binder.bind(ProjectEntityPathsResource.class);
+ binder.bind(ProjectRevisionEntitiesResource.class);
+ binder.bind(ProjectRevisionPureModelContextDataResource.class);
+ binder.bind(ProjectRevisionEntityPathsResource.class);
+ binder.bind(WorkspaceEntitiesResource.class);
+ binder.bind(WorkspacePureModelContextDataResource.class);
+ binder.bind(WorkspaceEntityPathsResource.class);
+ binder.bind(WorkspaceEntityChangesResource.class);
+ binder.bind(WorkspaceRevisionEntitiesResource.class);
+ binder.bind(WorkspaceRevisionPureModelContextDataResource.class);
+ binder.bind(WorkspaceRevisionEntityPathsResource.class);
+ binder.bind(VersionEntitiesResource.class);
+ binder.bind(VersionPureModelContextDataResource.class);
+ binder.bind(VersionEntityPathsResource.class);
+ binder.bind(VersionProjectConfigurationResource.class);
+ binder.bind(ProjectRevisionsResource.class);
+ binder.bind(ProjectEntityRevisionsResource.class);
+ binder.bind(ProjectPackageRevisionsResource.class);
+ binder.bind(WorkspaceRevisionsResource.class);
+ binder.bind(WorkspaceEntityRevisionsResource.class);
+ binder.bind(WorkspacePackageRevisionsResource.class);
+ binder.bind(IssuesResource.class);
+ binder.bind(UsersResource.class);
+ binder.bind(CurrentUserResource.class);
+ binder.bind(ReviewsOnlyResource.class);
+ binder.bind(ReviewsResource.class);
+ binder.bind(ProjectBuildsResource.class);
+ binder.bind(WorkspaceBuildsResource.class);
+ binder.bind(VersionBuildsResource.class);
+ binder.bind(VersionsResource.class);
+ binder.bind(ComparisonWorkspaceResource.class);
+ binder.bind(ComparisonReviewResource.class);
+ binder.bind(ComparisonReviewEntitiesResource.class);
+ binder.bind(ComparisonReviewProjectConfigurationResource.class);
+ binder.bind(ConflictResolutionProjectResource.class);
+ binder.bind(ConflictResolutionWorkspaceResource.class);
+ binder.bind(ConflictResolutionWorkspaceEntitiesResource.class);
+ binder.bind(ConflictResolutionWorkspaceEntityPathsResource.class);
+ binder.bind(ConflictResolutionWorkspaceRevisionsResource.class);
+ binder.bind(ConflictResolutionWorkspaceRevisionEntitiesResource.class);
+ binder.bind(ConflictResolutionWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(ConflictResolutionWorkspaceProjectConfigurationResource.class);
+ binder.bind(ConflictResolutionWorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(BackupProjectResource.class);
+ binder.bind(BackupWorkspaceResource.class);
+ binder.bind(BackupWorkspaceEntitiesResource.class);
+ binder.bind(BackupWorkspaceEntityPathsResource.class);
+ binder.bind(BackupWorkspaceRevisionsResource.class);
+ binder.bind(BackupWorkspaceRevisionEntitiesResource.class);
+ binder.bind(BackupWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(BackupWorkspaceProjectConfigurationResource.class);
+ binder.bind(BackupWorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(DownstreamDependenciesResource.class);
+ binder.bind(ProjectRevisionDependenciesResource.class);
+ binder.bind(ProjectVersionDependenciesResource.class);
+ binder.bind(WorkspaceRevisionDependenciesResource.class);
+ binder.bind(ProjectWorkflowsResource.class);
+ binder.bind(VersionWorkflowsResource.class);
+ binder.bind(WorkspaceWorkflowsResource.class);
+ binder.bind(ReviewWorkflowsResource.class);
+ binder.bind(ProjectWorkflowJobsResource.class);
+ binder.bind(VersionWorkflowJobsResource.class);
+ binder.bind(WorkspaceWorkflowJobsResource.class);
+ binder.bind(ReviewWorkflowJobsResource.class);
+ binder.bind(GroupBackupWorkspaceEntitiesResource.class);
+ binder.bind(GroupBackupWorkspaceEntityPathsResource.class);
+ binder.bind(GroupBackupWorkspaceProjectConfigurationResource.class);
+ binder.bind(GroupBackupWorkspaceResource.class);
+ binder.bind(GroupBackupWorkspaceRevisionEntitiesResource.class);
+ binder.bind(GroupBackupWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(GroupBackupWorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(GroupBackupWorkspaceRevisionsResource.class);
+ binder.bind(GroupComparisonWorkspaceResource.class);
+ binder.bind(GroupConflictResolutionWorkspaceEntitiesResource.class);
+ binder.bind(GroupConflictResolutionWorkspaceEntityPathsResource.class);
+ binder.bind(GroupConflictResolutionWorkspaceProjectConfigurationResource.class);
+ binder.bind(GroupConflictResolutionWorkspaceResource.class);
+ binder.bind(GroupConflictResolutionWorkspaceRevisionEntitiesResource.class);
+ binder.bind(GroupConflictResolutionWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(GroupConflictResolutionWorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(GroupConflictResolutionWorkspaceRevisionsResource.class);
+ binder.bind(GroupWorkspaceEntitiesResource.class);
+ binder.bind(GroupWorkspacePureModelContextDataResource.class);
+ binder.bind(GroupWorkspaceEntityChangesResource.class);
+ binder.bind(GroupWorkspaceEntityPathsResource.class);
+ binder.bind(GroupWorkspaceEntityRevisionsResource.class);
+ binder.bind(GroupWorkspacePackageRevisionsResource.class);
+ binder.bind(GroupWorkspaceProjectConfigurationResource.class);
+ binder.bind(GroupWorkspaceRevisionDependenciesResource.class);
+ binder.bind(GroupWorkspaceRevisionEntitiesResource.class);
+ binder.bind(GroupWorkspaceRevisionPureModelContextDataResource.class);
+ binder.bind(GroupWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(GroupWorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(GroupWorkspaceRevisionsResource.class);
+ binder.bind(GroupWorkspacesResource.class);
+ binder.bind(GroupWorkspaceWorkflowJobsResource.class);
+ binder.bind(GroupWorkspaceWorkflowsResource.class);
+ binder.bind(PatchProjectConfigurationResource.class);
+ binder.bind(PatchesResource.class);
+ binder.bind(PatchesWorkspacesResource.class);
+ binder.bind(PatchesGroupWorkspacesResource.class);
+ binder.bind(PatchesGroupWorkspaceProjectConfigurationResource.class);
+ binder.bind(PatchesProjectEntitiesResource.class);
+ binder.bind(PatchesProjectEntityPathsResource.class);
+ binder.bind(PatchesGroupWorkspaceEntitiesResource.class);
+ binder.bind(PatchesWorkspaceEntitiesResource.class);
+ binder.bind(PatchRevisionDependenciesResource.class);
+ binder.bind(PatchesGroupWorkspaceRevisionDependenciesResource.class);
+ binder.bind(PatchesWorkspaceRevisionDependenciesResource.class);
+ binder.bind(ConflictResolutionPatchResource.class);
+ binder.bind(ConflictResolutionPatchesWorkspaceEntitiesResource.class);
+ binder.bind(ConflictResolutionPatchesWorkspaceEntityPathsResource.class);
+ binder.bind(ConflictResolutionPatchesWorkspaceProjectConfigurationResource.class);
+ binder.bind(ConflictResolutionPatchesWorkspaceResource.class);
+ binder.bind(ConflictResolutionPatchesWorkspaceRevisionEntitiesResource.class);
+ binder.bind(ConflictResolutionPatchesWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(ConflictResolutionPatchesWorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(ConflictResolutionPatchesWorkspaceRevisionsResource.class);
+ binder.bind(ConflictResolutionPatchesGroupWorkspaceEntitiesResource.class);
+ binder.bind(ConflictResolutionPatchesGroupWorkspaceEntityPathsResource.class);
+ binder.bind(ConflictResolutionPatchesGroupWorkspaceProjectConfigurationResource.class);
+ binder.bind(ConflictResolutionPatchesGroupWorkspaceResource.class);
+ binder.bind(ConflictResolutionPatchesGroupWorkspaceRevisionEntitiesResource.class);
+ binder.bind(ConflictResolutionPatchesGroupWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(ConflictResolutionPatchesGroupWorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(ConflictResolutionPatchesGroupWorkspaceRevisionsResource.class);
+ binder.bind(PatchRevisionsResource.class);
+ binder.bind(PatchPackageRevisionsResource.class);
+ binder.bind(PatchEntityRevisionsResource.class);
+ binder.bind(PatchesGroupWorkspaceEntityRevisionsResource.class);
+ binder.bind(PatchesGroupWorkspaceRevisionsResource.class);
+ binder.bind(PatchesGroupWorkspacePackageRevisionsResource.class);
+ binder.bind(BackupPatchesWorkspaceEntitiesResource.class);
+ binder.bind(BackupPatchesWorkspaceEntityPathsResource.class);
+ binder.bind(BackupPatchesWorkspaceProjectConfigurationResource.class);
+ binder.bind(BackupPatchesWorkspaceResource.class);
+ binder.bind(BackupPatchesWorkspaceRevisionEntitiesResource.class);
+ binder.bind(BackupPatchesWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(BackupPatchesWorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(BackupPatchesWorkspaceRevisionsResource.class);
+ binder.bind(BackupPatchesGroupWorkspaceEntitiesResource.class);
+ binder.bind(BackupPatchesGroupWorkspaceEntityPathsResource.class);
+ binder.bind(BackupPatchesGroupWorkspaceProjectConfigurationResource.class);
+ binder.bind(BackupPatchesGroupWorkspaceResource.class);
+ binder.bind(BackupPatchesGroupWorkspaceRevisionEntitiesResource.class);
+ binder.bind(BackupPatchesGroupWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(BackupPatchesGroupWorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(BackupPatchesGroupWorkspaceRevisionsResource.class);
+ binder.bind(PatchesGroupWorkspaceRevisionPureModelContextDataResource.class);
+ binder.bind(PatchesGroupWorkspacePureModelContextDataResource.class);
+ binder.bind(PatchPureModelContextDataResource.class);
+ binder.bind(PatchRevisionPureModelContextDataResource.class);
+ binder.bind(PatchesWorkspacePureModelContextDataResource.class);
+ binder.bind(PatchReviewsResource.class);
+ binder.bind(ComparisonPatchReviewEntitiesResource.class);
+ binder.bind(ComparisonPatchReviewProjectConfigurationResource.class);
+ binder.bind(ComparisonPatchReviewResource.class);
+ binder.bind(ComparisonPatchesWorkspaceResource.class);
+ binder.bind(PatchesGroupComparisonWorkspaceResource.class);
+ binder.bind(PatchesGroupWorkspaceWorkflowJobsResource.class);
+ binder.bind(PatchesGroupWorkspaceWorkflowsResource.class);
+ binder.bind(PatchesWorkspaceWorkflowsResource.class);
+ binder.bind(PatchesWorkspaceWorkflowJobsResource.class);
+ binder.bind(PatchWorkflowJobsResource.class);
+ binder.bind(PatchWorkflowsResource.class);
+ binder.bind(PatchReviewWorkflowJobsResource.class);
+ binder.bind(PatchReviewWorkflowsResource.class);
+ binder.bind(PatchesGroupWorkspaceEntityPathsResource.class);
+ binder.bind(PatchesGroupWorkspaceEntityChangesResource.class);
+ binder.bind(PatchesGroupWorkspaceRevisionEntitiesResource.class);
+ binder.bind(PatchesGroupWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(PatchesWorkspaceEntityChangesResource.class);
+ binder.bind(PatchesWorkspaceEntityPathsResource.class);
+ binder.bind(PatchesWorkspaceRevisionEntitiesResource.class);
+ binder.bind(PatchesWorkspaceRevisionEntityPathsResource.class);
+ binder.bind(PatchesWorkspaceRevisionsResource.class);
+ binder.bind(PatchesGroupWorkspaceRevisionProjectConfigurationResource.class);
+ binder.bind(PatchesWorkspaceProjectConfigurationResource.class);
+ binder.bind(PatchesWorkspaceRevisionProjectConfigurationResource.class);
+ }
+
+ public void initRootDirectory(FSConfiguration fsConfiguration)
+ {
+ // Check if rootDirectory exists, and create if not
+ File localFile = new File(fsConfiguration.getRootDirectory());
+ if (!localFile.exists() && !localFile.mkdirs())
+ {
+ throw new RuntimeException("Failed to create directories for rootDirectory");
+ }
+ }
+
+ private void configureCommonApis(Binder binder)
+ {
+ binder.bind(DependenciesApi.class).to(DependenciesApiImpl.class);
+ }
+
+ protected void configureApis(Binder binder)
+ {
+ configureLegendApis(binder);
+ }
+
+ public void configureLegendApis(Binder binder)
+ {
+ binder.bind(MetadataApi.class).to(FileSystemMetadataApi.class);
+ binder.bind(ProjectApi.class).to(FileSystemProjectApi.class);
+ binder.bind(ProjectConfigurationApi.class).to(FileSystemProjectConfigurationApi.class);
+ binder.bind(UserApi.class).to(FileSystemUserApi.class);
+ binder.bind(IssueApi.class).to(FileSystemIssueApi.class);
+ binder.bind(EntityApi.class).to(FileSystemEntityApi.class);
+ binder.bind(WorkspaceApi.class).to(FileSystemWorkspaceApi.class);
+ binder.bind(RevisionApi.class).to(FileSystemRevisionApi.class);
+ binder.bind(ReviewApi.class).to(FileSystemReviewApi.class);
+ binder.bind(BuildApi.class).to(FileSystemBuildApi.class);
+ binder.bind(VersionApi.class).to(FileSystemVersionApi.class);
+ binder.bind(ComparisonApi.class).to(FileSystemComparisonApi.class);
+ binder.bind(ConflictResolutionApi.class).to(FileSystemConflictResolutionApi.class);
+ binder.bind(BackupApi.class).to(FileSystemBackupApi.class);
+ binder.bind(WorkflowApi.class).to(FileSystemWorkflowApi.class);
+ binder.bind(WorkflowJobApi.class).to(FileSystemWorkflowJobApi.class);
+ binder.bind(PatchApi.class).to(FileSystemPatchApi.class);
+ binder.bind(FileSystemAuthCheckResource.class);
+ binder.bind(FileSystemAuthResource.class);
+ }
+
+ private FSConfiguration getFSConfiguration()
+ {
+ return getConfiguration().getFileSystemConfiguration();
+ }
+
+ private ProjectStructureConfiguration getProjectStructureConfiguration()
+ {
+ ProjectStructureConfiguration projectStructureConfiguration = getConfiguration().getProjectStructureConfiguration();
+ return (projectStructureConfiguration == null) ? ProjectStructureConfiguration.emptyConfiguration() : projectStructureConfiguration;
+ }
+
+ private ProjectStructurePlatformExtensions buildProjectStructurePlatformExtensions()
+ {
+ return Optional.ofNullable(getConfiguration().getProjectStructureConfiguration())
+ .map(ProjectStructureConfiguration::getProjectPlatformsConfiguration)
+ .map(ProjectPlatformsConfiguration::buildProjectStructurePlatformExtensions)
+ .orElseGet(() -> ProjectStructurePlatformExtensions.newPlatformExtensions(Collections.emptyList(), Collections.emptyList()));
+ }
+
+ private ProjectStructureExtensionProvider getProjectStructureExtensionProvider()
+ {
+ if (this.extensionProvider == null)
+ {
+ this.extensionProvider = resolveProjectStructureExtensionProvider();
+ }
+ return this.extensionProvider;
+ }
+
+ private ProjectStructureExtensionProvider resolveProjectStructureExtensionProvider()
+ {
+ ProjectStructureConfiguration projectStructureConfiguration = getConfiguration().getProjectStructureConfiguration();
+ if (projectStructureConfiguration != null)
+ {
+ ProjectStructureExtensionProvider configuredProvider = projectStructureConfiguration.getProjectStructureExtensionProvider();
+ if (configuredProvider != null)
+ {
+ return configuredProvider;
+ }
+ List extensions = projectStructureConfiguration.getProjectStructureExtensions();
+ if ((extensions != null) && !extensions.isEmpty())
+ {
+ return DefaultProjectStructureExtensionProvider.fromExtensions(extensions);
+ }
+ }
+ return new VoidProjectStructureExtensionProvider();
+ }
+
+ private DepotConfiguration getDepotConfiguration()
+ {
+ DepotConfiguration depotConfiguration = getConfiguration().getDepotConfiguration();
+ return (depotConfiguration == null) ? DepotConfiguration.emptyConfiguration() : depotConfiguration;
+ }
+
+ private LegendSDLCServerFeaturesConfiguration getFeaturesConfiguration()
+ {
+ LegendSDLCServerFeaturesConfiguration featuresConfiguration = getConfiguration().getFeaturesConfiguration();
+ return (featuresConfiguration == null) ? LegendSDLCServerFeaturesConfiguration.emptyConfiguration() : featuresConfiguration;
+ }
+
+ private AuthClientInjector getAuthClientInjector()
+ {
+ if (this.authClientInjector == null)
+ {
+ this.authClientInjector = resolveAuthClientInjector();
+ }
+ return this.authClientInjector;
+ }
+
+ private AuthClientInjector resolveAuthClientInjector()
+ {
+ DepotConfiguration depotConfiguration = getConfiguration().getDepotConfiguration();
+ if (depotConfiguration != null)
+ {
+ AuthClientInjector authClientInjector = depotConfiguration.getAuthClientInjector();
+ if (authClientInjector != null)
+ {
+ return authClientInjector;
+ }
+ }
+ return builder -> builder;
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFS.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFS.java
new file mode 100644
index 0000000000..e39692f3ec
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFS.java
@@ -0,0 +1,128 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.startup;
+
+import com.hubspot.dropwizard.guicier.GuiceBundle;
+import io.dropwizard.lifecycle.Managed;
+import io.dropwizard.lifecycle.setup.LifecycleEnvironment;
+import io.dropwizard.setup.Bootstrap;
+import io.dropwizard.setup.Environment;
+import org.finos.legend.engine.protocol.pure.v1.PureProtocolObjectMapperFactory;
+import org.finos.legend.sdlc.server.BaseServer;
+import org.finos.legend.sdlc.server.depot.DepotConfiguration;
+import org.finos.legend.sdlc.server.project.config.ProjectStructureConfiguration;
+import org.finos.legend.sdlc.server.tools.BackgroundTaskProcessor;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.TimeUnit;
+
+public class LegendSDLCServerFS extends BaseServer
+{
+ private static final Logger LOGGER = LoggerFactory.getLogger(LegendSDLCServerFS.class);
+
+ private BackgroundTaskProcessor backgroundTaskProcessor;
+
+ public LegendSDLCServerFS()
+ {
+ }
+
+ @Override
+ public String getName()
+ {
+ return "Metadata SDLC";
+ }
+
+ @Override
+ public void initialize(Bootstrap bootstrap)
+ {
+ super.initialize(bootstrap);
+
+ configureApis(bootstrap);
+
+ // SDLC specific initialization
+ ProjectStructureConfiguration.configureObjectMapper(bootstrap.getObjectMapper());
+ DepotConfiguration.configureObjectMapper(bootstrap.getObjectMapper());
+ PureProtocolObjectMapperFactory.withPureProtocolExtensions(bootstrap.getObjectMapper());
+ }
+
+ protected void configureApis(Bootstrap bootstrap)
+ {
+ // Guice bootstrapping..
+ bootstrap.addBundle(buildGuiceBundle());
+ }
+
+ protected GuiceBundle buildGuiceBundle()
+ {
+ return GuiceBundle.defaultBuilder(LegendSDLCServerFSConfiguration.class)
+ .modules(buildBaseModule())
+ .build();
+ }
+
+ protected FSModule buildBaseModule()
+ {
+ return new FSModule(this);
+ }
+
+ @Override
+ public void run(T configuration, Environment environment)
+ {
+ super.run(configuration, environment);
+ LifecycleEnvironment lifecycleEnvironment = environment.lifecycle();
+ LOGGER.debug("Creating background task processor");
+ BackgroundTaskProcessor taskProcessor = new BackgroundTaskProcessor(1);
+ lifecycleEnvironment.manage(new Managed()
+ {
+ @Override
+ public void start()
+ {
+ // nothing to do
+ }
+
+ @Override
+ public void stop() throws Exception
+ {
+ LOGGER.debug("Shutting down background task processor");
+ taskProcessor.shutdown();
+ if (taskProcessor.awaitTermination(30, TimeUnit.SECONDS))
+ {
+ LOGGER.debug("Done shutting down background task processor");
+ }
+ else
+ {
+ LOGGER.debug("Background task processor did not terminate within the timeout");
+ }
+ }
+ });
+ this.backgroundTaskProcessor = taskProcessor;
+ }
+
+ public BackgroundTaskProcessor getBackgroundTaskProcessor()
+ {
+ return this.backgroundTaskProcessor;
+ }
+
+ @Override
+ protected ServerPlatformInfo newServerPlatformInfo()
+ {
+ return new ServerPlatformInfo(null, null, null);
+ }
+
+ public static void main(String... args) throws Exception
+ {
+ LOGGER.info("Starting SDLC server with File System as backend");
+ new LegendSDLCServerFS().run(args);
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFSConfiguration.java b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFSConfiguration.java
new file mode 100644
index 0000000000..1c5efeb014
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/java/org/finos/legend/sdlc/server/startup/LegendSDLCServerFSConfiguration.java
@@ -0,0 +1,29 @@
+// Copyright 2023 Goldman Sachs
+//
+// 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.finos.legend.sdlc.server.startup;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import org.finos.legend.sdlc.server.config.LegendSDLCServerConfiguration;
+
+public class LegendSDLCServerFSConfiguration extends LegendSDLCServerConfiguration
+{
+ @JsonProperty("fileSystem")
+ private FSConfiguration fileSystemConfiguration;
+
+ public FSConfiguration getFileSystemConfiguration()
+ {
+ return this.fileSystemConfiguration;
+ }
+}
diff --git a/legend-sdlc-server-fs/src/main/resources/docker/config/config.json b/legend-sdlc-server-fs/src/main/resources/docker/config/config.json
new file mode 100644
index 0000000000..b3b63fa761
--- /dev/null
+++ b/legend-sdlc-server-fs/src/main/resources/docker/config/config.json
@@ -0,0 +1,120 @@
+{
+ "applicationName": "Legend SDLC",
+ "sessionCookie": "LEGEND_SDLC_JSESSIONID",
+ "server": {
+ "adminContextPath": "/admin",
+ "applicationConnectors": [
+ {
+ "type": "http",
+ "port": ${SDLC_PORT},
+ "maxRequestHeaderSize": "128KiB"
+ }
+ ],
+ "adminConnectors": [
+ {
+ "type": "http",
+ "port": ${SDLC_ADMIN_PORT}
+ }
+ ],
+ "gzip": {
+ "includedMethods": [
+ "GET",
+ "POST"
+ ]
+ },
+ "requestLog": {
+ "type": "classic",
+ "level": "OFF",
+ "appenders": [
+ {
+ "type": "console",
+ "logFormat": "OFF"
+ }
+ ]
+ },
+ "rootPath": "/api"
+ },
+ "projectStructure": {
+ "projectCreation": {
+ },
+ "extensionProvider": {
+ "org.finos.legend.sdlc.server.gitlab.finos.FinosGitlabProjectStructureExtensionProvider": {}
+ },
+ "platforms": {
+ "legend-engine": {
+ "groupId": "org.finos.legend.engine",
+ "platformVersion": {
+ "version": "${ENGINE_MAVEN_VERSION}"
+ }
+ },
+ "legend-sdlc": {
+ "groupId": "org.finos.legend.sdlc",
+ "platformVersion":{
+ "version": "${SDLC_MAVEN_VERSION}"
+ }
+ }
+ }
+ },
+ "filterPriorities": {
+ "GitLab": 1,
+ "org.pac4j.j2e.filter.CallbackFilter": 2,
+ "org.pac4j.j2e.filter.SecurityFilter": 3,
+ "CORS": 4
+ },
+ "pac4j": {
+ "callbackPrefix": "/api/pac4j/login",
+ "clients": [
+ {
+ "org.finos.legend.server.pac4j.gitlab.GitlabClient": {
+ "name": "gitlab",
+ "clientId": "${GITLAB_APP_ID}",
+ "secret": "${GITLAB_APP_SECRET}",
+ "discoveryUri": "https://${GITLAB_HOST}/.well-known/openid-configuration",
+ "scope": "openid profile api"
+ }
+ }
+ ],
+ "mongoSession": {
+ "enabled": "${MONGO_SESSION_ENABLED}",
+ "collection": "userSessions"
+ },
+ "bypassPaths": [
+ "/api/info"
+ ]
+ },
+ "fileSystem": {
+ "rootDirectory": ${FS_ROOT_DIR}
+ },
+ "features": {
+ "canCreateProject": true,
+ "canCreateVersion": true
+ },
+ "gitLab": {
+ "newProjectVisibility": "public",
+ "projectTag": "${SDLC_PROJECT_TAG}",
+ "server": {
+ "scheme": "https",
+ "host": "${GITLAB_HOST}"
+ },
+ "app": {
+ "id": "${GITLAB_APP_ID}",
+ "secret": "${GITLAB_APP_SECRET}",
+ "redirectURI": "${SDLC_REDIRECT_URI}"
+ }
+ },
+ "logging": {
+ "level": "INFO",
+ "appenders": [
+ {
+ "type": "console",
+ "logFormat": "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%thread] %c - %m%n"
+ }
+ ]
+ },
+ "swagger": {
+ "resourcePackage": "org.finos.legend.sdlc.server.resources",
+ "title": "Legend LEGEND_SDLC",
+ "version": "local-snapshot",
+ "schemes": []
+ }
+}
\ No newline at end of file
diff --git a/legend-sdlc-server-fs/src/test/resources/config.yml b/legend-sdlc-server-fs/src/test/resources/config.yml
new file mode 100644
index 0000000000..391f6cf34c
--- /dev/null
+++ b/legend-sdlc-server-fs/src/test/resources/config.yml
@@ -0,0 +1,90 @@
+# Copyright 2023 Goldman Sachs
+#
+# 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.
+
+
+applicationName: Legend SDLC
+
+sessionCookie: LEGEND_SDLC_JSESSIONID
+
+cors:
+ allowedHeaders:
+ - X-Requested-With
+ - Content-Type
+ - Accept
+ - Origin
+ - Access-Control-Allow-Credentials
+ - x-b3-parentspanid
+ - x-b3-sampled
+ - x-b3-spanid
+ - x-b3-traceid
+ - legend-test-pat
+
+server:
+ applicationConnectors:
+ - type: http
+ port: 3000
+ maxRequestHeaderSize: 128KiB
+ adminConnectors:
+ - type: http
+ port: 8889
+ gzip:
+ includedMethods:
+ - GET
+ - POST
+ requestLog:
+ type: classic
+ level: OFF
+ appenders:
+ - type: console
+ logFormat: "OFF"
+ rootPath: /api
+
+filterPriorities:
+ GitLab: 1
+ org.pac4j.j2e.filter.CallbackFilter: 2
+ org.pac4j.j2e.filter.SecurityFilter: 3
+ CORS: 4
+
+pac4j:
+ clients:
+ - org.pac4j.core.client.direct.AnonymousClient: {}
+ bypassPaths:
+ - /api/info
+ - /api/server/info
+ - /api/server/platforms
+ - /api/auth/authorized
+
+fileSystem:
+ rootDirectory: /root/AlloyProjects
+
+features:
+ canCreateProject: true
+ canCreateVersion: false
+
+projectStructure:
+ projectCreation:
+ groupIdPattern: ^org\.finos\.legend\..+
+
+logging:
+ # Change this to affect library class logging
+ level: INFO
+ appenders:
+ - type: console
+ logFormat: "%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%thread] %c - %m%n"
+
+swagger:
+ resourcePackage: org.finos.legend.sdlc.server.resources
+ title: Legend SDLC
+ version: local-snapshot
+ schemes: []
\ No newline at end of file
diff --git a/pom.xml b/pom.xml
index 7ba333bb8d..f2136170d7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -38,6 +38,7 @@
legend-sdlc-language-pure-compiler
legend-sdlc-server-shared
legend-sdlc-server
+ legend-sdlc-server-fs
legend-sdlc-generation-shared
legend-sdlc-generation-file
legend-sdlc-generation-service
@@ -541,6 +542,11 @@
test-jar
test
+
+ org.finos.legend.sdlc
+ legend-sdlc-server-fs
+ ${project.version}
+