Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,15 @@ public ItemDomainLogbook completeCreateEntityInstance(ItemDomainLogbook newLogbo
}

public ItemDomainLogbook completeCreateEntityInstance(ItemDomainLogbook newLogbookDoc, EntityType logbookType, UserInfo userInfo, boolean attachDefaultTemplate) throws CdbException, CloneNotSupportedException {
if (logbookType == null) {
throw new InvalidObjectState("A logbook type is required to create a log document.");
}
if (logbookType.isHasChildren()) {
throw new InvalidObjectState(String.format(
"'%s' is a grouping logbook type and cannot contain log documents. Select one of its child logbook types.",
logbookType.getAvailableLongDisplayName()));
}

newLogbookDoc.setEntityTypeList(new ArrayList<>());
EntityType entityType = logbookType;
newLogbookDoc.getEntityTypeList().add(entityType);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,13 @@ public void setDescription(String description) {
this.description = description;
}

public String getParentDisplayName() {
if (getParentEntityType() != null) {
return getParentEntityType().getDisplayName();
}
return null;
}

@XmlTransient
public List<EntityType> getAllowedEntityTypeList() {
return allowedEntityTypeList;
Expand Down Expand Up @@ -240,7 +247,6 @@ public void setIsInternal(Boolean isInternal) {
this.isInternal = isInternal;
}

@XmlTransient
public List<EntityType> getEntityTypeChildren() {
return entityTypeChildren;
}
Expand Down Expand Up @@ -280,7 +286,7 @@ public void setDefaultOwnerUserGroup(UserGroup defaultOwnerUserGroup) {

@JsonIgnore
public boolean isHasChildren() {
return !entityTypeChildren.isEmpty();
return entityTypeChildren != null && !entityTypeChildren.isEmpty();
}

@JsonIgnore
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ public UserInfo getCurrentUser() {

// Delegates to the shared helper so REST and MCP filter logbook types identically.
public List<EntityType> getLogbookTypes() {
return LogbookDomainUtility.getLogbookTypes(domainFacade);
return LogbookDomainUtility.getLogbookTypes(domainFacade, false);
}

// Delegates to the shared helper so REST and MCP resolve systems identically.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,19 @@ public class LogbookRoute extends ItemBaseRoute {
@Operation(responses = {
@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)})
@Produces(MediaType.APPLICATION_JSON)
public List<EntityType> getLogbookTypes() {
return LogbookDomainUtility.getLogbookTypes(domainFacade);
public List<EntityType> getLogbookTypes(
@Parameter(description = "Include logbook types that have children (grouping types).")
@QueryParam("includeAll") boolean includeAll) {
return LogbookDomainUtility.getLogbookTypes(domainFacade, includeAll);
}

@GET
@Path("/LogbookTypeHierarchy")
@Operation(responses = {
@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)})
@Produces(MediaType.APPLICATION_JSON)
public List<EntityType> getLogbookTypeHierarchy() {
return LogbookDomainUtility.getLogbookTypeHierarchy(domainFacade);
}

// Delegates to the shared helper; behavior is unchanged, the item type list is returned unfiltered.
Expand Down Expand Up @@ -114,7 +125,7 @@ public List<ItemDomainLogbook> getLogbookTemplates() {
@ApiResponse(responseCode = "200", description = "OK", useReturnTypeSchema = true)})
@Produces(MediaType.APPLICATION_JSON)
public List<ItemDomainLogbook> getLogDocuments(@PathParam("logbookTypeId") int logbookTypeId, @PathParam("limit") int rowLimit) throws InvalidArgument {
List<EntityType> logbookTypes = getLogbookTypes();
List<EntityType> logbookTypes = LogbookDomainUtility.getLogbookTypes(domainFacade, false);
EntityType logbookType = null;

for (EntityType type : logbookTypes) {
Expand Down Expand Up @@ -479,7 +490,7 @@ private ItemDomainLogbook getLogDocumentById(int logDocumentId) throws InvalidAr
}

private EntityType verifyLogbookTypeArgument(Integer logbookTypeId) throws InvalidArgument {
List<EntityType> logbookTypes = getLogbookTypes();
List<EntityType> logbookTypes = LogbookDomainUtility.getLogbookTypes(domainFacade, false);

for (EntityType logbookType : logbookTypes) {
if (logbookType.getId() == logbookTypeId) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ public SearchEntitiesResults genericSearch(@RequestBody(required = true) SearchE

// Delegates to the shared helper, which copies before removing the template type instead of filtering the domain's managed list in place.
private List<EntityType> getLogbookTypes() {
return LogbookDomainUtility.getLogbookTypes(domainFacade);
return LogbookDomainUtility.getLogbookTypes(domainFacade, false);
}

// Delegates to the shared helper; behavior is unchanged, the item type list is returned unfiltered.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import gov.anl.aps.logr.portal.model.db.entities.ItemType;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

// Shared logbook domain lookups for the REST and MCP layers; copies before filtering so the domain's managed collection is never modified.
Expand All @@ -25,38 +26,60 @@ public static Domain getLogbookDomain(DomainFacade domainFacade) {
return domainFacade == null ? null : domainFacade.find(ItemDomainName.LOGBOOK_ID);
}

// Convenience overload so callers holding only the facade need no domain lookup of their own.
public static List<EntityType> getLogbookTypes(DomainFacade domainFacade) {
return getLogbookTypes(getLogbookDomain(domainFacade));
public static List<EntityType> getLogbookTypes(DomainFacade domainFacade, boolean includeParents) {
return getLogbookTypes(getLogbookDomain(domainFacade), includeParents);
}

public static List<EntityType> getLogbookTypeHierarchy(DomainFacade domainFacade) {
return getLogbookTypeHierarchy(getLogbookDomain(domainFacade));
}

// Convenience overload so callers holding only the facade need no domain lookup of their own.
public static List<ItemType> getLogbookSystems(DomainFacade domainFacade) {
return getLogbookSystems(getLogbookDomain(domainFacade));
}

// Allowed logbook types minus the template type, as a new list; never returns the managed collection.
public static List<EntityType> getLogbookTypes(Domain domain) {
// Callers may hold no domain; return an empty, modifiable list rather than throwing.
if (domain == null) {
return new ArrayList<>();
public static List<EntityType> getLogbookTypes(Domain domain, boolean includeParents) {
List<EntityType> logbookTypes = getAllLogbookTypes(domain);
if (!includeParents) {
logbookTypes.removeIf(type -> type != null && type.isHasChildren());
}
return logbookTypes;
}

public static List<EntityType> getLogbookTypeHierarchy(Domain domain) {
List<EntityType> roots = getAllLogbookTypes(domain);
roots.removeIf(type -> type == null || type.getParentEntityType() != null);
roots.sort(Comparator.comparing(EntityType::getSortOrder,
Comparator.nullsLast(Comparator.naturalOrder())));
for (EntityType root : roots) {
initializeChildren(root);
}
return roots;
}

List<EntityType> allowedEntityTypeList = domain.getAllowedEntityTypeList();
// A domain with no configured allowed types is valid, not an error.
if (allowedEntityTypeList == null) {
private static List<EntityType> getAllLogbookTypes(Domain domain) {
if (domain == null || domain.getAllowedEntityTypeList() == null) {
return new ArrayList<>();
}

// Copy before filtering; never mutate the domain's managed collection.
List<EntityType> logbookTypes = new ArrayList<>(allowedEntityTypeList);
List<EntityType> logbookTypes = new ArrayList<>(domain.getAllowedEntityTypeList());
String templateName = EntityTypeName.template.getValue();
// Null-tolerant exact-name match, preserving the original filtering semantics.
logbookTypes.removeIf(t -> t != null && templateName.equals(t.getName()));

logbookTypes.removeIf(type -> type != null && templateName.equals(type.getName()));
return logbookTypes;
}

private static void initializeChildren(EntityType entityType) {
List<EntityType> children = entityType.getEntityTypeChildren();
if (children == null) {
return;
}
children.size();
for (EntityType child : children) {
initializeChildren(child);
}
}

// Systems configured for the logbook domain; unfiltered, empty when unavailable.
public static List<ItemType> getLogbookSystems(Domain domain) {
// Nothing filters this list, so the managed collection can be returned as-is.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright (c) UChicago Argonne, LLC. All rights reserved.
* See LICENSE file.
*/
package gov.anl.aps.logr.portal.controllers.utilities;

import gov.anl.aps.logr.common.exceptions.InvalidObjectState;
import gov.anl.aps.logr.portal.model.db.entities.Domain;
import gov.anl.aps.logr.portal.model.db.entities.EntityInfo;
import gov.anl.aps.logr.portal.model.db.entities.EntityType;
import gov.anl.aps.logr.portal.model.db.entities.ItemDomainLogbook;
import java.util.ArrayList;
import java.util.Arrays;

public class LogbookTypeLockoutTest {

public static void main(String[] args) throws Exception {
int passed = 0;
int failed = 0;
for (java.lang.reflect.Method method : LogbookTypeLockoutTest.class.getDeclaredMethods()) {
if (method.getName().startsWith("test") && method.getParameterCount() == 0) {
try {
method.setAccessible(true);
method.invoke(null);
System.out.println("PASS " + method.getName());
passed++;
} catch (java.lang.reflect.InvocationTargetException ex) {
System.out.println("FAIL " + method.getName() + ": " + ex.getCause());
failed++;
}
}
}
System.out.println(passed + " passed, " + failed + " failed");
if (failed > 0) {
System.exit(1);
}
}

static void check(boolean condition, String message) {
if (!condition) {
throw new AssertionError(message);
}
}

static void testGroupingTypeIsRejected() throws Exception {
EntityType parent = new EntityType(1, "Operations");
parent.setDisplayName("Operations");
EntityType child = new EntityType(2, "Storage-Ring");
parent.setEntityTypeChildren(new ArrayList<>(Arrays.asList(child)));

try {
new ItemDomainLogbookControllerUtility().completeCreateEntityInstance(
new ItemDomainLogbook(), parent, null, false);
throw new AssertionError("a grouping type must be rejected");
} catch (InvalidObjectState ex) {
check(ex.getMessage().contains("grouping logbook type"),
"the rejection must explain that the type is a grouping type");
}
}

static void testNullTypeIsRejected() throws Exception {
try {
new ItemDomainLogbookControllerUtility().completeCreateEntityInstance(
new ItemDomainLogbook(), null, null, false);
throw new AssertionError("a null logbook type must be rejected");
} catch (InvalidObjectState ex) {
check(ex.getMessage().contains("required"),
"the rejection must explain that a logbook type is required");
}
}

static void testLeafTypeIsAccepted() throws Exception {
EntityType leaf = new EntityType(1, "Storage-Ring");
leaf.setEntityTypeChildren(new ArrayList<>());
Domain domain = new Domain();
domain.setAllowedEntityTypeList(new ArrayList<>(Arrays.asList(leaf)));
ItemDomainLogbook document = new ItemDomainLogbook();
document.init(domain, new EntityInfo());

ItemDomainLogbook result = new ItemDomainLogbookControllerUtility()
.completeCreateEntityInstance(document, leaf, null, false);

check(result == document, "the utility must return the supplied document");
check(result.getEntityTypeList().size() == 1
&& result.getEntityTypeList().get(0).equals(leaf),
"the leaf type must be assigned to the document");
}
}
Loading
Loading