Skip to content

Commit

Permalink
BXC-4678 chompb command to list projects (#105)
Browse files Browse the repository at this point in the history
* list_projects service and command

* fix tests

* imageFormats constant, add image formats, lowercase file extension, allowedActions return list, add test, remove jackson.version

* allowedAction JsonArray, fix imageFormats, fix test

* allowedActions ArrayNode, fix tests

* add try/catch

* fix project path

* fix tests
  • Loading branch information
krwong authored Aug 29, 2024
1 parent 1f75569 commit 3746798
Show file tree
Hide file tree
Showing 5 changed files with 494 additions and 1 deletion.
3 changes: 2 additions & 1 deletion src/main/java/edu/unc/lib/boxc/migration/cdm/CLIMain.java
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
FilterIndexCommand.class,
AggregateFilesCommand.class,
PermissionsCommand.class,
ExportObjectsCommand.class
ExportObjectsCommand.class,
ListProjectsCommand.class
})
public class CLIMain implements Callable<Integer> {
@Option(names = { "-w", "--work-dir" },
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package edu.unc.lib.boxc.migration.cdm;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.unc.lib.boxc.migration.cdm.exceptions.MigrationException;
import edu.unc.lib.boxc.migration.cdm.services.CdmFieldService;
import edu.unc.lib.boxc.migration.cdm.services.CdmIndexService;
import edu.unc.lib.boxc.migration.cdm.services.ListProjectsService;
import edu.unc.lib.boxc.migration.cdm.services.ProjectPropertiesService;
import edu.unc.lib.boxc.migration.cdm.services.SourceFileService;
import org.slf4j.Logger;
import picocli.CommandLine.Command;
import picocli.CommandLine.ParentCommand;

import java.io.IOException;
import java.util.concurrent.Callable;

import static edu.unc.lib.boxc.migration.cdm.util.CLIConstants.outputLogger;
import static org.slf4j.LoggerFactory.getLogger;

/**
* @author krwong
*/
@Command(name = "list_projects",
description = "List all chompb projects in a directory.")
public class ListProjectsCommand implements Callable<Integer> {
private static final Logger log = getLogger(ListProjectsCommand.class);

@ParentCommand
private CLIMain parentCommand;

private CdmFieldService fieldService;
private CdmIndexService indexService;
private ProjectPropertiesService propertiesService;
private SourceFileService sourceFileService;
private ListProjectsService listProjectsService;

@Override
public Integer call() {
try {
initialize();
ObjectMapper mapper = new ObjectMapper();
JsonNode listProjects = listProjectsService.listProjects(parentCommand.getWorkingDirectory());
String prettyPrintList = mapper.writerWithDefaultPrettyPrinter().writeValueAsString(listProjects);
outputLogger.info(prettyPrintList);
return 0;
} catch(MigrationException | IllegalArgumentException e) {
outputLogger.info("List projects failed: {}", e.getMessage());
return 1;
} catch(Exception e) {
log.error("Failed to list projects", e);
outputLogger.info("List projects failed: {}", e.getMessage(), e);
return 1;
}
}

private void initialize() throws IOException {
fieldService = new CdmFieldService();
indexService = new CdmIndexService();
indexService.setFieldService(fieldService);
sourceFileService = new SourceFileService();
sourceFileService.setIndexService(indexService);
propertiesService = new ProjectPropertiesService();
listProjectsService = new ListProjectsService();
listProjectsService.setFieldService(fieldService);
listProjectsService.setIndexService(indexService);
listProjectsService.setSourceFileService(sourceFileService);
listProjectsService.setPropertiesService(propertiesService);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
package edu.unc.lib.boxc.migration.cdm.services;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import edu.unc.lib.boxc.migration.cdm.exceptions.InvalidProjectStateException;
import edu.unc.lib.boxc.migration.cdm.model.MigrationProject;
import edu.unc.lib.boxc.migration.cdm.model.SourceFilesInfo;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
* Service for listing all chompb projects in a given directory
* @author krwong
*/
public class ListProjectsService {
private static final Logger log = LoggerFactory.getLogger(ListProjectsService.class);

private CdmFieldService fieldService;
private CdmIndexService indexService;
private ProjectPropertiesService propertiesService;
private SourceFileService sourceFileService;

public static final String PROJECT_PATH = "projectPath";
public static final String STATUS = "status";
public static final String ALLOWED_ACTIONS = "allowedActions";
private static final Set<String> IMAGE_FORMATS = new HashSet<>(Arrays.asList("tif", "tiff", "jpeg", "jpg", "png",
"gif", "pict", "bmp", "psd", "jp2", "nef", "crw", "cr2", "dng", "raf"));

/**
* List projects in given directory
* @return jsonNode of projects
*/
public JsonNode listProjects(Path directory) throws Exception {
if (Files.notExists(directory)) {
throw new InvalidProjectStateException("Path " + directory + " does not exist");
}

// create JSON
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false);
ArrayNode arrayNode = mapper.createArrayNode();

for (File file : directory.toFile().listFiles()) {
if (file.isDirectory()) {
try {
MigrationProject project = initializeProject(file.toPath());

Path projectPath = file.toPath().toAbsolutePath();
String projectStatus = status(project);
ArrayNode allowedActions = mapper.valueToTree(allowedActions(project));
JsonNode projectProperties = mapper.readTree(project.getProjectPropertiesPath().toFile());

// add project info to JSON
ObjectNode objectNode = mapper.createObjectNode();
objectNode.put(PROJECT_PATH, projectPath.toString());
objectNode.put(STATUS, projectStatus);
objectNode.putArray(ALLOWED_ACTIONS).addAll(allowedActions);
objectNode.set("projectProperties", projectProperties);
arrayNode.add(objectNode);
} catch (Exception e) {
log.error(e.getMessage());
}
}
}
log.debug(arrayNode.toString());

return arrayNode;
}

/**
* Condensed representation of the phase a project is in
* possible values: initialized, indexed, sources_mapped, sips_generated, ingested, archived
* @return project status
*/
private String status(MigrationProject project) {
String status = null;

// TODO: add archived state
// if () {
// status = "archived";
// }
if (!project.getProjectProperties().getSipsSubmitted().isEmpty()) {
status = "ingested";
} else if (project.getProjectProperties().getSipsGeneratedDate() != null) {
status = "sips_generated";
} else if (project.getProjectProperties().getSourceFilesUpdatedDate() != null) {
status = "sources_mapped";
} else if (project.getProjectProperties().getIndexedDate() != null) {
status = "indexed";
} else if (Files.exists(project.getProjectPropertiesPath())) {
status = "initialized";
}

return status;
}

/**
* Tell other applications (boxc) what processes are supported by the project
* crop_color_bars: populated if source files are mapped and if project contains any images (based off source file extensions)
* @return allowed_actions
*/
private List<String> allowedActions(MigrationProject project) throws Exception {
List<String> allowedActions = new ArrayList<>();

if (project.getProjectProperties().getSourceFilesUpdatedDate() != null) {
SourceFilesInfo info = sourceFileService.loadMappings();
if (info.getMappings().stream().map(entry ->
FilenameUtils.getExtension(entry.getFirstSourcePath().toString().toLowerCase()))
.anyMatch(IMAGE_FORMATS::contains)) {
allowedActions.add("crop_color_bars");
}
}
return allowedActions;
}

public MigrationProject initializeProject(Path path) throws Exception {
MigrationProject project = MigrationProjectFactory.loadMigrationProject(path);
propertiesService.setProject(project);
sourceFileService.setProject(project);
indexService.setProject(project);
fieldService.setProject(project);

return project;
}

public void setFieldService(CdmFieldService fieldService) {
this.fieldService = fieldService;
}

public void setIndexService(CdmIndexService indexService) {
this.indexService = indexService;
}

public void setPropertiesService(ProjectPropertiesService propertiesService) {
this.propertiesService = propertiesService;
}

public void setSourceFileService(SourceFileService sourceFileService) {
this.sourceFileService = sourceFileService;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package edu.unc.lib.boxc.migration.cdm;

import edu.unc.lib.boxc.migration.cdm.model.MigrationProject;
import edu.unc.lib.boxc.migration.cdm.services.ListProjectsService;
import edu.unc.lib.boxc.migration.cdm.services.MigrationProjectFactory;
import edu.unc.lib.boxc.migration.cdm.test.BxcEnvironmentHelper;
import edu.unc.lib.boxc.migration.cdm.test.CdmEnvironmentHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.nio.file.Path;

public class ListProjectsCommandIT extends AbstractCommandIT {
private static final String PROJECT_ID_2 = "proj2";

@BeforeEach
public void setup() throws Exception {
initProjectAndHelper();
setupChompbConfig();
}

@Test
public void invalidDirectoryTest() throws Exception {
String[] args = new String[] {
"-w", String.valueOf(Path.of("test")),
"list_projects" };
executeExpectFailure(args);

assertOutputContains("does not exist");
}

@Test
public void listProjectsTest() throws Exception {
String[] args = new String[] {
"-w", String.valueOf(baseDir),
"list_projects" };
executeExpectSuccess(args);

assertOutputContains("\"" + ListProjectsService.PROJECT_PATH + "\" : \"" + Path.of(baseDir + "/" + PROJECT_ID) + "\"");
assertOutputContains("\"" + ListProjectsService.STATUS + "\" : \"initialized\"");
assertOutputContains("\"" + ListProjectsService.ALLOWED_ACTIONS + "\" : [ ]");
assertOutputContains("\"name\" : \"" + PROJECT_ID + "\"");
}

@Test
public void listMultipleProjectsTest() throws Exception {
project = MigrationProjectFactory.createMigrationProject(
tmpFolder, PROJECT_ID_2, null, "user", CdmEnvironmentHelper.DEFAULT_ENV_ID,
BxcEnvironmentHelper.DEFAULT_ENV_ID, MigrationProject.PROJECT_SOURCE_CDM);

String[] args = new String[] {
"-w", String.valueOf(baseDir),
"list_projects" };
executeExpectSuccess(args);

assertOutputContains("\"" + ListProjectsService.PROJECT_PATH + "\" : \"" +
Path.of(baseDir + "/" + PROJECT_ID) + "\"");
assertOutputContains("\"" + ListProjectsService.PROJECT_PATH + "\" : \"" +
Path.of(baseDir + "/" + PROJECT_ID_2) + "\"");
assertOutputContains("\""+ ListProjectsService.STATUS + "\" : \"initialized\"");
assertOutputContains("\"" + ListProjectsService.ALLOWED_ACTIONS + "\" : [ ]");
assertOutputContains("\"name\" : \"" + PROJECT_ID + "\"");
assertOutputContains("\"name\" : \"" + PROJECT_ID_2 + "\"");
}
}
Loading

0 comments on commit 3746798

Please sign in to comment.