Skip to content
Open
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
4 changes: 3 additions & 1 deletion db/sql/create_cdb_tables.sql
Original file line number Diff line number Diff line change
Expand Up @@ -362,11 +362,13 @@ CREATE TABLE `item_element` (
KEY `item_element_k3` (`contained_item_id2`),
KEY `item_element_k4` (`entity_info_id`),
KEY `item_element_k5` (`derived_from_item_element_id`),
KEY `item_element_k6` (`represents_item_element_id`),
CONSTRAINT `item_element_fk1` FOREIGN KEY (`parent_item_id`) REFERENCES `item` (`id`) ON UPDATE CASCADE,
CONSTRAINT `item_element_fk2` FOREIGN KEY (`contained_item_id1`) REFERENCES `item` (`id`) ON UPDATE CASCADE,
CONSTRAINT `item_element_fk3` FOREIGN KEY (`contained_item_id2`) REFERENCES `item` (`id`) ON UPDATE CASCADE,
CONSTRAINT `item_element_fk4` FOREIGN KEY (`entity_info_id`) REFERENCES `entity_info` (`id`) ON UPDATE CASCADE,
CONSTRAINT `item_element_fk5` FOREIGN KEY (`derived_from_item_element_id`) REFERENCES `item_element` (`id`) ON UPDATE CASCADE ON DELETE SET NULL
CONSTRAINT `item_element_fk5` FOREIGN KEY (`derived_from_item_element_id`) REFERENCES `item_element` (`id`) ON UPDATE CASCADE ON DELETE SET NULL,
CONSTRAINT `item_element_fk6` FOREIGN KEY (`represents_item_element_id`) REFERENCES `item_element` (`id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;

--
Expand Down
23 changes: 23 additions & 0 deletions db/sql/scripts/cleanup-dangling-item-element-representations.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
--
-- Copyright (c) UChicago Argonne, LLC. All rights reserved.
-- See LICENSE file.
--
-- Review dangling representation references before clearing them.
-- Execute by running `mysql CDB_DB_NAME -h 127.0.0.1 -u cdb -p < cleanup-dangling-item-element-representations.sql`

SELECT
representing.id,
representing.parent_item_id,
representing.represents_item_element_id
FROM item_element AS representing
LEFT JOIN item_element AS represented
ON represented.id = representing.represents_item_element_id
WHERE representing.represents_item_element_id IS NOT NULL
AND represented.id IS NULL;

UPDATE item_element AS representing
LEFT JOIN item_element AS represented
ON represented.id = representing.represents_item_element_id
SET representing.represents_item_element_id = NULL
WHERE representing.represents_item_element_id IS NOT NULL
AND represented.id IS NULL;
6 changes: 6 additions & 0 deletions db/sql/updates/updateTo3.19.0.sql
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@

-- Execute by running `mysql CDB_DB_NAME -h 127.0.0.1 -u cdb -p < updateTo3.19.0.sql`

SELECT 'If adding item_element_fk6 reports dangling references, review and run db/sql/scripts/cleanup-dangling-item-element-representations.sql before retrying this update.' AS recommendation;

ALTER TABLE item_element
ADD KEY `item_element_k6` (`represents_item_element_id`),
ADD CONSTRAINT `item_element_fk6` FOREIGN KEY (`represents_item_element_id`) REFERENCES `item_element` (`id`) ON UPDATE CASCADE;

DROP VIEW IF EXISTS v_ioc_owner_info;

CREATE VIEW v_ioc_owner_info AS
Expand Down
5 changes: 5 additions & 0 deletions docs/release-notes/3.19.0.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
# Machine Design

* Added safe multilevel representation of catalog assembly elements in machine hierarchies.
* Prevented unlinking, replacement, promotion, or deletion from leaving invalid or duplicate representation references.

# Inventory

* Added housing assignment, reassignment, replacement, and clearing from inventory details.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3491,12 +3491,6 @@ public void prepareMachineAssignRepresentingAssemblyElement() {
}

if (parentMachineDesign != null) {
ItemElement representsCatalogElement = parentMachineDesign.getRepresentsCatalogElement();
if (representsCatalogElement != null) {
SessionUtility.addErrorMessage("Represented Parent", "Parent item is represented by another assembly part, cannot link another level of representation.");
return;
}

Item assignedItem = parentMachineDesign.getAssignedItem();

if (assignedItem != null && assignedItem.getItemElementDisplayList().size() != 0) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -723,10 +723,80 @@ private void updateTemplateReferenceForAssignedItem(
}

public void updateRepresentingAssemblyElementForMachine(ItemDomainMachineDesign node, ItemElement element, boolean matchElementNamesForTemplateInstances) throws InvalidArgument, InvalidObjectState {
preflightRepresentingAssemblyElementUpdate(node, element, matchElementNamesForTemplateInstances, false);
node.clearItemsToUpdate();
updateRepresentingAssemblyElementForMachine(node, element, matchElementNamesForTemplateInstances, false);
}

private void preflightRepresentingAssemblyElementUpdate(ItemDomainMachineDesign node,
ItemElement representedElement, boolean matchElementNamesForTemplateInstances,
boolean updateFromTemplate) throws InvalidArgument, InvalidObjectState {
ItemElement resolvedElement = resolveRepresentedElement(node, representedElement,
matchElementNamesForTemplateInstances, updateFromTemplate);
Item resultingAssignment = resolvedElement == null
? node.getSelfElement().getContainedItem2() : resolvedElement.getContainedItem();
validateRepresentedChildrenForAssignment(node, resultingAssignment);

if (node.getIsItemTemplate()) {
List<ItemDomainMachineDesign> instances
= (List<ItemDomainMachineDesign>) (List<?>) node.getItemsCreatedFromThisTemplateItem();
for (ItemDomainMachineDesign instance : instances) {
preflightRepresentingAssemblyElementUpdate(instance, representedElement,
matchElementNamesForTemplateInstances, true);
}
}
}

private ItemElement resolveRepresentedElement(ItemDomainMachineDesign node,
ItemElement representedElement, boolean matchElementNamesForTemplateInstances,
boolean updateFromTemplate) throws InvalidArgument, InvalidObjectState {
if (representedElement == null) {
return null;
}

List<ItemElement> elements = fetchElementsAvaiableForNodeRepresentation(node);
if (elements != null && elements.contains(representedElement)) {
if (node.getSelfElement().getContainedItem2() != null) {
throw new InvalidObjectState(node.getName() + " has an assigned item set.");
}
return representedElement;
}

if (elements != null && matchElementNamesForTemplateInstances && updateFromTemplate) {
for (ItemElement assemblyElement : elements) {
if (assemblyElement.getName().equals(representedElement.getName())) {
return assemblyElement;
}
}
}

throw new InvalidArgument("Element provided is not an available element for representation for node "
+ node.getName() + ".");
}

private void validateRepresentedChildrenForAssignment(ItemDomainMachineDesign node,
Item resultingAssignment) throws InvalidObjectState {
Item catalogAssignment = resultingAssignment;
if (catalogAssignment != null && catalogAssignment.getDerivedFromItem() != null) {
catalogAssignment = catalogAssignment.getDerivedFromItem();
}
List<ItemElement> resultingElements = catalogAssignment == null
? new ArrayList<>() : catalogAssignment.getItemElementDisplayList();

for (ItemElement machineChild : node.getItemElementDisplayList()) {
Item containedItem = machineChild.getContainedItem();
if (containedItem instanceof ItemDomainMachineDesign) {
ItemDomainMachineDesign child = (ItemDomainMachineDesign) containedItem;
ItemElement childRepresentation = child.getRepresentsCatalogElement();
if (childRepresentation != null && !resultingElements.contains(childRepresentation)) {
throw new InvalidObjectState("Cannot update represented element for " + node.getName()
+ " while child " + child.getName()
+ " represents an element outside the resulting assembly.");
}
}
}
}

private void updateRepresentingAssemblyElementForMachine(ItemDomainMachineDesign node, ItemElement representedElement, boolean matchElementNamesForTemplateInstances, boolean updateFromTemplate) throws InvalidArgument, InvalidObjectState {
// Ensure this can only be applied for standard machine elements and template items.
if (node.getEntityTypeList().size() == 1) {
Expand All @@ -750,26 +820,10 @@ private void updateRepresentingAssemblyElementForMachine(ItemDomainMachineDesign
}
}

ItemDomainMachineDesign parentMachineDesign = node.getParentMachineDesign();
if (representedElement != null && parentMachineDesign.getRepresentsCatalogElement() != null) {
String errMessage = node.getName();
errMessage += " has a parent " + parentMachineDesign.getName();
errMessage += " with represented element reference.";
throw new InvalidObjectState(errMessage);
}

List<ItemElement> elements = fetchElementsAvaiableForNodeRepresentation(node);

// Updating of element
if (representedElement != null) {
if (elements != null && elements.contains(representedElement)) {
// Valid element. Update the element set.
if (node.getAssignedItem() != null) {
String errMessage = node.getName();
errMessage += " has an assigned item set.";
throw new InvalidObjectState(errMessage);
}
} else {
if (elements == null || !elements.contains(representedElement)) {
boolean found = false;
if (elements != null && matchElementNamesForTemplateInstances && updateFromTemplate) {
String elementName = representedElement.getName();
Expand All @@ -786,8 +840,13 @@ private void updateRepresentingAssemblyElementForMachine(ItemDomainMachineDesign
throw new InvalidArgument("Element provided is not an available element for representation for node " + node.getName() + ".");
}
}
} else {
// No additional validation needed for clearing represented element.
validateElementAvailableForRepresentation(node.getParentMachineDesign(), node, representedElement);

if (node.getSelfElement().getContainedItem2() != null) {
String errMessage = node.getName();
errMessage += " has an assigned item set.";
throw new InvalidObjectState(errMessage);
}
}

node.setRepresentsCatalogElement(representedElement);
Expand All @@ -804,9 +863,26 @@ private void updateRepresentingAssemblyElementForMachine(ItemDomainMachineDesign
}
}

public void validateElementAvailableForRepresentation(ItemDomainMachineDesign parentMachineDesign,
ItemDomainMachineDesign node, ItemElement representedElement) throws InvalidArgument {
if (representedElement == null) {
throw new InvalidArgument("Assembly element provided for representation was not found.");
}

List<ItemElement> availableElements = fetchElementsAvaiableForNodeRepresentation(parentMachineDesign, node);
if (availableElements == null || !availableElements.contains(representedElement)) {
String parentName = parentMachineDesign == null ? "N/A" : parentMachineDesign.getName();
throw new InvalidArgument("Element provided is not an available element for representation under parent machine "
+ parentName + ".");
}
}

public List<ItemElement> fetchElementsAvaiableForNodeRepresentation(ItemDomainMachineDesign node) {
ItemDomainMachineDesign parentMachineDesign = node.getParentMachineDesign();
return fetchElementsAvaiableForNodeRepresentation(node.getParentMachineDesign(), node);
}

private List<ItemElement> fetchElementsAvaiableForNodeRepresentation(ItemDomainMachineDesign parentMachineDesign,
ItemDomainMachineDesign nodeToIgnore) {
List<ItemElement> availableElementsForNodeRepresentation = null;

if (parentMachineDesign != null) {
Expand All @@ -820,13 +896,14 @@ public List<ItemElement> fetchElementsAvaiableForNodeRepresentation(ItemDomainMa
availableElementsForNodeRepresentation = new ArrayList<>();
availableElementsForNodeRepresentation.addAll(assignedCat.getItemElementDisplayList());

// Remove any already assigned elements.
// Remove elements represented by siblings.
List<ItemElement> machineChildren = parentMachineDesign.getItemElementDisplayList();

for (ItemElement machineChild : machineChildren) {
ItemDomainMachineDesign containedItem = (ItemDomainMachineDesign) machineChild.getContainedItem();
ItemElement representingElement = containedItem.getRepresentsCatalogElement();
availableElementsForNodeRepresentation.remove(representingElement);
if (!ObjectUtility.equals(containedItem, nodeToIgnore)) {
availableElementsForNodeRepresentation.remove(containedItem.getRepresentsCatalogElement());
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,22 +57,27 @@ public ItemElement performMachineMoveWithUpdate(ItemDomainMachineDesign newParen
*/
public ItemDomainMachineDesign createRepresentingMachineForAssemblyElement(ItemDomainMachineDesign parentMachine,
ItemElement catalogElement, UserInfo creatorUser) throws InvalidArgument, CdbException {
if (catalogElement == null) {
throw new InvalidArgument("Assembly element provided for representation was not found.");
}

Item parentItem = catalogElement.getParentItem();
if ((parentItem instanceof ItemDomainCatalog
|| parentItem instanceof ItemDomainInventory) == false) {
if (!(parentItem instanceof ItemDomainCatalog)
&& !(parentItem instanceof ItemDomainInventory)) {
throw new InvalidArgument("Cannot create representing machine element for selected item.");
}

// Create new machine placeholder
ItemElement machinePlaceholder = null;
machinePlaceholder = prepareMachinePlaceholder(parentMachine, creatorUser);
parentMachine.getFullItemElementList().add(machinePlaceholder);

// Ensure to get catalog element
// Inventory assembly elements represent their source catalog element.
ItemElement derivedFromItemElement = catalogElement.getDerivedFromItemElement();
if (derivedFromItemElement != null) {
catalogElement = derivedFromItemElement;
}
validateElementAvailableForRepresentation(parentMachine, null, catalogElement);

// Create new machine placeholder
ItemElement machinePlaceholder = null;
machinePlaceholder = prepareMachinePlaceholder(parentMachine, creatorUser);
parentMachine.getFullItemElementList().add(machinePlaceholder);

ItemDomainMachineDesign newMachine = (ItemDomainMachineDesign) machinePlaceholder.getContainedItem();
newMachine.setRepresentsCatalogElement(catalogElement);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,12 @@ protected void prepareEntityDestroy(ItemElement itemElement, UserInfo userInfo)
super.prepareEntityDestroy(itemElement, userInfo);
// Verify that item domain allows destroy of item element.

ItemElementConstraintInformation ieci = getFreshItemElementConstraintInformation(itemElement);
Item parentItem = itemElement.getParentItem();
if (!(parentItem instanceof ItemDomainCatalog)) {
ItemElementConstraintInformation ieci = getFreshItemElementConstraintInformation(itemElement);
if (!(parentItem instanceof ItemDomainCatalog) || ieci.isReferencedByRepresentingMachine()) {
if (ieci.isSafeToRemove() == false) {
String constraintMessage = generateSingleDeleteConstraintMessage(itemElement);
throw new CdbException("Cannot remove item element. Constrains not met. " + constraintMessage);
throw new CdbException("Cannot remove item element. Constraints not met. " + constraintMessage);
}
}
}
Expand All @@ -56,6 +56,9 @@ private String generateSingleDeleteConstraintMessage(ItemElement itemElement) {
ItemElementConstraintInformation constraint = getItemElementConstraintInformation(itemElement);
String message = null;

if (constraint.isReferencedByRepresentingMachine()) {
return "Item element is represented by a machine design element. Unlink it before continuing.";
}
if (constraint instanceof CatalogItemElementConstraintInformation) {
CatalogItemElementConstraintInformation catalogConstraint = (CatalogItemElementConstraintInformation) constraint;
message = generateCatalogSpecificPreventDeleteUpdateContainedMessage(catalogConstraint);
Expand Down Expand Up @@ -137,6 +140,10 @@ protected void prepareEntityUpdate(ItemElement itemElement, UserInfo userInfo) t
ItemElementConstraintInformation ieci = getItemElementConstraintInformation(freshDbItemElement);
if (ieci.isSafeToUpdateContainedItem() == false) {
itemElement.setContainedItem(originalContainedItem);
if (ieci.hasRepresentedMachineChildren()) {
throw new CdbException("Cannot replace the item assigned to element " + itemElement
+ " while its representing machine has represented children. Unlink the children before continuing.");
}
throw new CdbException("Cannot update item element " + itemElement + " due to constraints not met. Please reload the item details page and try again.");
}
}
Expand Down
Loading
Loading