Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clean up old HItem related to CoMPAS #268

Merged
merged 3 commits into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -8,7 +8,6 @@ public class CompasSclDataServiceErrorCode {
throw new UnsupportedOperationException("CompasSclDataRepositoryErrorCode class");
}

public static final String UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE = "SDS-0001";
public static final String CREATION_ERROR_CODE = "SDS-0002";
public static final String UNMARSHAL_ERROR_CODE = "SDS-0003";
public static final String HEADER_NOT_FOUND_ERROR_CODE = "SDS-0004";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,13 @@
package org.lfenergy.compas.scl.data.model;

import org.eclipse.microprofile.openapi.annotations.media.Schema;
import org.lfenergy.compas.scl.data.exception.CompasSclDataServiceException;

import java.util.Objects;

import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE;

@Schema(description = "Presenting the version logic used in CoMPAS.")
public class Version {
public class Version implements Comparable<Version> {
public static final String PATTERN = "([1-9]\\d*)\\.(\\d+)\\.(\\d+)";

@Schema(description = "The major version.", example = "2")
private final int majorVersion;
@Schema(description = "The minor version.", example = "1")
Expand Down Expand Up @@ -40,7 +39,7 @@ private void validate(String version) {
if (version == null || version.isEmpty()) {
throw new IllegalArgumentException("Version can't be null or empty");
}
if (!version.matches("([1-9]\\d*)\\.(\\d+)\\.(\\d+)")) {
if (!version.matches(PATTERN)) {
throw new IllegalArgumentException("Version is in the wrong format. Must consist of 3 number separated by dot (1.3.5)");
}
}
Expand All @@ -50,16 +49,11 @@ public Version getNextVersion(ChangeSetType changeSetType) {
throw new IllegalArgumentException("ChangeSetType can't be null or empty");
}

switch (changeSetType) {
case MAJOR:
return new Version(majorVersion + 1, 0, 0);
case MINOR:
return new Version(majorVersion, minorVersion + 1, 0);
case PATCH:
return new Version(majorVersion, minorVersion, patchVersion + 1);
default:
throw new CompasSclDataServiceException(UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE, "Unhandled ChangeSetType " + changeSetType);
}
return switch (changeSetType) {
case MAJOR -> new Version(majorVersion + 1, 0, 0);
case MINOR -> new Version(majorVersion, minorVersion + 1, 0);
case PATCH -> new Version(majorVersion, minorVersion, patchVersion + 1);
};
}

public int getMajorVersion() {
Expand Down Expand Up @@ -92,6 +86,17 @@ public boolean equals(final Object obj) {
}
}

@Override
public int compareTo(Version otherVersion) {
if (this.majorVersion == otherVersion.getMajorVersion()) {
if (this.minorVersion == otherVersion.getMinorVersion()) {
return Integer.compare(this.patchVersion, otherVersion.getPatchVersion());
}
return Integer.compare(this.minorVersion, otherVersion.getMinorVersion());
}
return Integer.compare(this.majorVersion, otherVersion.getMajorVersion());
}

@Override
public String toString() {
return majorVersion + "." + minorVersion + "." + patchVersion;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,9 +98,50 @@ public Element addCompasElement(Element compasPrivate, String localName, String
return element;
}

/**
* The method will remove all newer Hitem Element, including the version passed from the History Element.
* It will search for Hitem Element where the Revision Attribute is empty and the version has the
* pattern "Major version"."Minor version"."Patch version".
* <p>
* If the version is the same or newer the Hitem will be removed from the History Element.
*
* @param headerElement The Header Element containing the History Items.
* @param version The version from which to remove.
*/
public void cleanupHistoryItem(Element headerElement, Version version) {
var history = getChildNodesByName(headerElement, SCL_HISTORY_ELEMENT_NAME, SCL_NS_URI).stream().findFirst();
history.ifPresent(historyElement ->
getChildNodesByName(historyElement, SCL_HITEM_ELEMENT_NAME, SCL_NS_URI)
.stream()
.filter(hItemElement -> hItemElement.getAttribute("revision").isBlank())
.forEach(hItemElement -> {
if (shouldRemoveHItem(hItemElement, version)) {
historyElement.removeChild(hItemElement);
}
})
);
}

/**
* Check if the version uses the pattern that matches the one used by CoMPAS and if this is the case
* compare the two versions and determine if the HItem version is smaller or the same as the new one
* being created.
*
* @param hItemElement The HItem Element to check the version attribute from.
* @param version The new version that will be created.
* @return True if the HItem has a smaller or the same version and should be removed.
*/
boolean shouldRemoveHItem(Element hItemElement, Version version) {
var hItemVersion = hItemElement.getAttribute("version");
if (hItemVersion.isBlank() || !hItemVersion.matches(Version.PATTERN)) {
return false;
}
return version.compareTo(new Version(hItemVersion)) <= 0;
}

/**
* Add a Hitem to the History Element from the Header. If the Header doesn't contain a History Element
* this Element will also be created. The revision attribute will be empty, the when will be set to the
* this Element will also be created. The revision attribute will be empty, the "when" will be set to the
* current date.
*
* @param header The Header Element from SCL under which the History Element can be found/added.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE;
import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.CREATION_ERROR_CODE;

class CompasSclDataServiceExceptionTest {
@Test
void constructor_WhenCalledWithOnlyMessage_ThenMessageCanBeRetrieved() {
String expectedMessage = "The message";
CompasSclDataServiceException exception =
new CompasSclDataServiceException(UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE, expectedMessage);
new CompasSclDataServiceException(CREATION_ERROR_CODE, expectedMessage);

assertEquals(expectedMessage, exception.getMessage());
}
Expand All @@ -23,7 +23,7 @@ void constructor_WhenCalledWithCauseAndMessage_ThenCauseAndMessageCanBeRetrieved
String expectedMessage = "The message";
Exception expectedCause = new RuntimeException();
CompasSclDataServiceException exception =
new CompasSclDataServiceException(UNKNOWN_CHANGE_SET_TYPE_ERROR_CODE, expectedMessage, expectedCause);
new CompasSclDataServiceException(CREATION_ERROR_CODE, expectedMessage, expectedCause);

assertEquals(expectedMessage, exception.getMessage());
assertEquals(expectedCause, exception.getCause());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ void constructor_WhenCalledWithNullOrEmptyValue_ThenExceptionThrown() {
var expectedMessage = "Version can't be null or empty";

assertThrows(IllegalArgumentException.class, () -> new Version(null), expectedMessage);

assertThrows(IllegalArgumentException.class, () -> new Version(""), expectedMessage);
}

Expand Down Expand Up @@ -93,4 +92,25 @@ void equals_WhenTwoSameObject_ThenEqualsShouldAlsoBeTheSame() {
void toString_WhenCalled_ThenCorrectStringReturned() {
assertEquals("1.5.8", new Version(1, 5, 8).toString());
}

@Test
void compareTo_WhenWhenMajorVersionAreDifferent_ThenCorrectResult() {
assertEquals(-1, new Version("1.0.0").compareTo(new Version("2.0.0")));
assertEquals(0, new Version("1.0.0").compareTo(new Version("1.0.0")));
assertEquals(1, new Version("2.0.0").compareTo(new Version("1.0.0")));
}

@Test
void compareTo_WhenWhenMinorVersionAreDifferent_ThenCorrectResult() {
assertEquals(-1, new Version("1.1.0").compareTo(new Version("1.2.0")));
assertEquals(0, new Version("1.1.0").compareTo(new Version("1.1.0")));
assertEquals(1, new Version("1.2.0").compareTo(new Version("1.1.0")));
}

@Test
void compareTo_WhenWhenPathVersionAreDifferent_ThenCorrectResult() {
assertEquals(-1, new Version("1.1.1").compareTo(new Version("1.1.2")));
assertEquals(0, new Version("1.1.1").compareTo(new Version("1.1.1")));
assertEquals(1, new Version("1.1.2").compareTo(new Version("1.1.1")));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
import org.lfenergy.compas.scl.data.model.Version;
import org.w3c.dom.Element;

import java.util.List;
import java.util.Optional;

import static org.junit.jupiter.api.Assertions.*;
import static org.lfenergy.compas.scl.data.SclDataServiceConstants.*;
import static org.lfenergy.compas.scl.data.exception.CompasSclDataServiceErrorCode.HEADER_NOT_FOUND_ERROR_CODE;
Expand Down Expand Up @@ -188,6 +191,73 @@ void getAttributeValue_WhenCalledForNonExistingAttribute_ThenOptionalEmptyReturn
assertFalse(result.isPresent());
}

@Test
void cleanupHistoryItem_WhenCalledWithVersion_ThenSameAndNewerVersionsAreRemoved() {
var scl = readSCL("scl_cleanup_history.scd");

assertEquals(7, getHItems(scl).size());
processor.cleanupHistoryItem(processor.getSclHeader(scl).orElseThrow(), new Version("1.0.2"));
assertEquals(5, getHItems(scl).size());
}

@Test
void shouldRemoveHItem_WhenCalledWithInvalidVersion_ThenFalseReturned() {
var scl = readSCL("scl_cleanup_history.scd");
var hItem = getHItem(scl, "Siemens");

assertFalse(processor.shouldRemoveHItem(hItem, new Version("1.0.2")));
}

@Test
void shouldRemoveHItem_WhenCalledWithEmptyVersion_ThenFalseReturned() {
var scl = readSCL("scl_cleanup_history.scd");
var hItem = getHItem(scl, "Empty");

assertFalse(processor.shouldRemoveHItem(hItem, new Version("1.0.2")));
}

@Test
void shouldRemoveHItem_WhenCalledWithOlderVersion_ThenFalseReturned() {
var scl = readSCL("scl_cleanup_history.scd");
var hItem = getHItem(scl, "Created");

assertFalse(processor.shouldRemoveHItem(hItem, new Version("1.0.2")));
}

@Test
void shouldRemoveHItem_WhenCalledWithSameVersion_ThenTrueReturned() {
var scl = readSCL("scl_cleanup_history.scd");
var hItem = getHItem(scl, "Updated 1");

assertTrue(processor.shouldRemoveHItem(hItem, new Version("1.0.2")));
}

@Test
void shouldRemoveHItem_WhenCalledWithNewerVersion_ThenTrueReturned() {
var scl = readSCL("scl_cleanup_history.scd");
var hItem = getHItem(scl, "Updated 2");

assertTrue(processor.shouldRemoveHItem(hItem, new Version("1.0.2")));
}

private List<Element> getHItems(Element scl) {
return processor.getSclHeader(scl)
.map(header -> processor.getChildNodeByName(header, SCL_HISTORY_ELEMENT_NAME, SCL_NS_URI))
.filter(Optional::isPresent)
.map(Optional::get)
.map(history -> processor.getChildNodesByName(history, SCL_HITEM_ELEMENT_NAME, SCL_NS_URI))
.stream()
.flatMap(List::stream)
.toList();
}

private Element getHItem(Element scl, String what) {
return getHItems(scl).stream()
.filter(element -> element.getAttribute("what").equals(what))
.findFirst()
.get();
}

private Element readSCL(String sclFilename) {
var inputStream = getClass().getResourceAsStream("/scl/" + sclFilename);
assert inputStream != null;
Expand Down
17 changes: 17 additions & 0 deletions repository/src/test/resources/scl/scl_cleanup_history.scd
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<!-- SPDX-FileCopyrightText: 2021 Alliander N.V. -->
<!-- -->
<!-- SPDX-License-Identifier: Apache-2.0 -->
<SCL xmlns="http://www.iec.ch/61850/2003/SCL" version="2007" revision="B" release="4">
<Header id="370e5d89-df3a-4d9b-a651-3dc5cb28bee1" version="1.0.0">
<History>
<Hitem revision="" version="0.0.1" what="Init" when="2021-09-01T01:57:56+02:00" who="Mr Editor"/>
<Hitem revision="" version="1.0.0" what="Created" when="2021-09-01T01:57:56+02:00" who="Mr Editor"/>
<Hitem revision="" version="1.0.2" what="Updated 1" when="2021-09-01T01:57:56+02:00" who="Mr Editor"/>
<Hitem revision="" version="2.0.0" what="Updated 2" when="2021-09-01T01:57:56+02:00" who="Mr Editor"/>
<Hitem revision="" version="Siemens" what="Siemens" when="2021-09-01T01:57:56+02:00" who="Mr Editor"/>
<Hitem revision="revision" version="Siemens" what="Siemens revision" when="2021-09-01T01:57:56+02:00"
who="Mr Editor"/>
<Hitem revision="" version="" what="Empty" when="2021-09-01T01:57:56+02:00" who="Mr Editor"/>
</History>
</Header>
</SCL>
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ public String create(SclFileType type, String name, String who, String comment,

// Update the Header of the SCL (or create if not exists.)
var header = createOrUpdateHeader(scl, id, version);
createHistoryItem(header, "SCL created", who, comment, version);
sclElementProcessor.cleanupHistoryItem(header, version);
sclElementProcessor.addHistoryItem(header, who, createMessage("SCL created", comment), version);

// Update or add the Compas Private Element to the SCL File.
setSclCompasPrivateElement(scl, name, type);
Expand All @@ -149,7 +150,7 @@ public String create(SclFileType type, String name, String who, String comment,
/**
* Create a new version of a specific SCL XML File. The content will be the passed SCL XML File.
* The UUID and new version (depending on the passed ChangeSetType) are set and
* the CoMPAS Private elements will also be copied, the SCL Name will only be copied if not available in the passed
* the CoMPAS Private elements will also be copied, the SCL Name will only be copied if it isn't available in the
* SCL XML File.
*
* @param type The type to update it for.
Expand Down Expand Up @@ -180,7 +181,8 @@ public String update(SclFileType type, UUID id, ChangeSetType changeSetType, Str

// Update the Header of the SCL (or create if not exists.)
var header = createOrUpdateHeader(scl, id, version);
createHistoryItem(header, "SCL updated", who, comment, version);
sclElementProcessor.cleanupHistoryItem(header, version);
sclElementProcessor.addHistoryItem(header, who, createMessage("SCL updated", comment), version);

// Update or add the Compas Private Element to the SCL File.
var newSclName = newFileName.orElse(currentSclMetaInfo.getName());
Expand Down Expand Up @@ -278,20 +280,18 @@ private void setSclCompasPrivateElement(Element scl, String name, SclFileType fi
}

/**
* Add a Hitem to the current or added History Element from the Header.
* If a comment is added by the user, a standard message will be joined together with the comment from the user.
*
* @param header The header of SCL file to edit.
* @param message The message set on the Hitem.
* @param who The user that made the change.
* @param comment If filled the comment that will be added to the message.
* @param version The version set on the Hitem.
* @param standardMessage The standard message.
* @param comment The comment a user may have added.
* @return The full message to be added to the HItem.
*/
private void createHistoryItem(Element header, String message, String who, String comment, Version version) {
var fullmessage = message;
private String createMessage(String standardMessage, String comment) {
var message = standardMessage;
if (comment != null && !comment.isBlank()) {
fullmessage += ", " + comment;
message += ", " + comment;
}
sclElementProcessor.addHistoryItem(header, who, fullmessage, version);
return message;
}

/**
Expand Down
Loading