From 6cf5092af07a9b66a7bd44e1c9bd20631500ce2d Mon Sep 17 00:00:00 2001 From: Lars Johansson <lars.johansson@ess.eu> Date: Thu, 24 Feb 2022 12:06:35 +0100 Subject: [PATCH] Added REST API endpoints for verification of data migration Verification of data migration with focus on device revision. Verification of data migration with focus on name part revision. Verification of data migration with focus on all data can be reached Verification of data migration with focus on difference for content between old REST API and new REST API. --- .../org/openepics/names/old/model/Device.java | 66 ++ .../names/old/model/DeviceRevision.java | 232 ++++ .../openepics/names/old/model/NamePart.java | 88 ++ .../names/old/model/NamePartRevision.java | 284 +++++ .../names/old/model/NamePartType.java | 32 + .../org/openepics/names/old/model/Role.java | 38 + .../names/old/model/UserAccount.java | 87 ++ .../names/old/nameviews/NameViewProvider.java | 180 +++ .../old/IDeviceRevisionRepository.java | 32 + .../old/INamePartRevisionRepository.java | 32 + .../controller/VerificationController.java | 1045 +++++++++++++++++ 11 files changed, 2116 insertions(+) create mode 100644 src/main/java/org/openepics/names/old/model/Device.java create mode 100644 src/main/java/org/openepics/names/old/model/DeviceRevision.java create mode 100644 src/main/java/org/openepics/names/old/model/NamePart.java create mode 100644 src/main/java/org/openepics/names/old/model/NamePartRevision.java create mode 100644 src/main/java/org/openepics/names/old/model/NamePartType.java create mode 100644 src/main/java/org/openepics/names/old/model/Role.java create mode 100644 src/main/java/org/openepics/names/old/model/UserAccount.java create mode 100644 src/main/java/org/openepics/names/old/nameviews/NameViewProvider.java create mode 100644 src/main/java/org/openepics/names/repository/old/IDeviceRevisionRepository.java create mode 100644 src/main/java/org/openepics/names/repository/old/INamePartRevisionRepository.java create mode 100644 src/main/java/org/openepics/names/rest/controller/VerificationController.java diff --git a/src/main/java/org/openepics/names/old/model/Device.java b/src/main/java/org/openepics/names/old/model/Device.java new file mode 100644 index 0000000..aa2565a --- /dev/null +++ b/src/main/java/org/openepics/names/old/model/Device.java @@ -0,0 +1,66 @@ +/*- + * Copyright (c) 2014 European Spallation Source ERIC. + * Copyright (c) 2014 Cosylab d.d. + * + * This file is part of Naming Service. + * Naming Service is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or any newer version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt + */ + +package org.openepics.names.old.model; + +import com.google.common.base.Preconditions; + +import javax.persistence.Entity; + +import org.openepics.names.repository.model.Persistable; + +import java.util.Objects; +import java.util.UUID; + +/** + * An entity representing a device. + * + * @author Marko Kolar + */ +@Entity +public class Device extends Persistable { + + private static final long serialVersionUID = 8489649071981076533L; + private String uuid; + + protected Device() {} + + /** + * Constructs a new device entity based on a UUID identifier. + * + * @param uuid the universally unique identifier + */ + public Device(UUID uuid) { + Preconditions.checkNotNull(uuid); + this.uuid = uuid.toString(); + } + + /** + * @return The universally unique identifier. + */ + public UUID getUuid() { return UUID.fromString(uuid); } + + @Override public boolean equals(Object other) { + return other instanceof Device && ((Device) other).getUuid().equals(getUuid()); + } + + @Override + public int hashCode() { + return Objects.hash(getUuid()); + } +} diff --git a/src/main/java/org/openepics/names/old/model/DeviceRevision.java b/src/main/java/org/openepics/names/old/model/DeviceRevision.java new file mode 100644 index 0000000..c7efa5a --- /dev/null +++ b/src/main/java/org/openepics/names/old/model/DeviceRevision.java @@ -0,0 +1,232 @@ +/*- + * Copyright (c) 2014 European Spallation Source ERIC. + * Copyright (c) 2014 Cosylab d.d. + * + * This file is part of Naming Service. + * Naming Service is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or any newer version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt + */ + +package org.openepics.names.old.model; + +import com.google.common.base.Preconditions; + +import javax.annotation.Nullable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.openepics.names.repository.model.Persistable; + +import java.util.Date; + +/** + * A revision of a Device entity representing its state at some point in time. + * + * @author Marko Kolar + */ +@Entity +@Table(name = "devicerevision") +public class DeviceRevision extends Persistable { + + private static final long serialVersionUID = -7697708696131775826L; + + @JoinColumn(name = "device_id") + private @ManyToOne Device device; + + @JoinColumn(name = "requestedby_id") + private @ManyToOne UserAccount requestedBy; + + @Column(name = "requestdate") + private Date requestDate; + + private boolean deleted; + + @JoinColumn(name = "section_id") + private @ManyToOne NamePart section; + + @JoinColumn(name = "devicetype_id") + private @ManyToOne NamePart deviceType; + + @Column(name = "instanceindex") + private @Nullable String instanceIndex; + + @Column(name = "conventionname") + private String conventionName; + + @Column(name = "conventionnameeqclass") + private String conventionNameEqClass; + + @Column(name = "additionalinfo") + private @Nullable String additionalInfo; + + @Column(name = "processorcomment") + private @Nullable String processorComment; + + protected DeviceRevision() {} + + /** + * Constructs a new device revision entity (data object with relations). + * + * @param device the device the revision pertains to + * @param requestDate the time when the revision was proposed + * @param requestedBy the user that proposed the revision. Null if the revision was generated by an automated + * process. + * @param deleted a flag signifying that the revision represents deletion of the device + * @param section the section containing the device + * @param deviceType the type of the device + * @param instanceIndex an additional identifier that, in combination with other attributes, determine the unique + * convention name of the device. Null if omitted. + * @param conventionName the full name of the device in accordance with the naming convention + * @param conventionNameEqClass the representative of the equivalence class the convention name belongs to. This is + * used to ensure uniqueness of convention names when treating similar looking names (for example, containing 0 vs. + * O, 1 vs. l) as equal. + * @param additionalInfo Additional information (description, comment etc) about the device. + */ + public DeviceRevision( + Device device, Date requestDate, @Nullable UserAccount requestedBy, boolean deleted, + NamePart section, NamePart deviceType, @Nullable String instanceIndex, String conventionName, + String conventionNameEqClass, @Nullable String additionalInfo) { + + this(device, requestDate, requestedBy, deleted, section, deviceType, instanceIndex, conventionName, + conventionNameEqClass, additionalInfo, null); + } + + /** + * Constructs a new device revision entity (data object with relations). + * + * @param device the device the revision pertains to + * @param requestDate the time when the revision was proposed + * @param requestedBy the user that proposed the revision. Null if the revision was generated by an automated + * process. + * @param deleted a flag signifying that the revision represents deletion of the device + * @param section the section containing the device + * @param deviceType the type of the device + * @param instanceIndex an additional identifier that, in combination with other attributes, determine the unique + * convention name of the device. Null if omitted. + * @param conventionName the full name of the device in accordance with the naming convention + * @param conventionNameEqClass the representative of the equivalence class the convention name belongs to. This is + * used to ensure uniqueness of convention names when treating similar looking names (for example, containing 0 vs. + * O, 1 vs. l) as equal. + * @param additionalInfo additional information (description, comment etc) about the device + * @param commitMessage commit message + */ + public DeviceRevision( + Device device, Date requestDate, @Nullable UserAccount requestedBy, boolean deleted, + NamePart section, NamePart deviceType, @Nullable String instanceIndex, String conventionName, + String conventionNameEqClass, @Nullable String additionalInfo, @Nullable String commitMessage) { + + Preconditions.checkNotNull(device); + Preconditions.checkNotNull(requestDate); + Preconditions.checkNotNull(section); + // not check deviceType as device may have section but no deviceType + Preconditions.checkArgument(instanceIndex == null || !instanceIndex.isEmpty()); + Preconditions.checkArgument(conventionName != null && !conventionName.isEmpty()); + Preconditions.checkArgument(conventionNameEqClass != null); + Preconditions.checkArgument(additionalInfo == null || !additionalInfo.isEmpty()); + this.device = device; + this.requestedBy = requestedBy; + this.requestDate = new Date(requestDate.getTime()); + this.deleted = deleted; + this.section = section; + this.deviceType = deviceType; + this.instanceIndex = instanceIndex; + this.conventionName = conventionName; + this.conventionNameEqClass = conventionNameEqClass; + this.additionalInfo=additionalInfo; + this.processorComment=commitMessage; + } + + /** + * @return The device the revision pertains to. + */ + public Device getDevice() { return device; } + + /** + * @return The time when the revision was proposed. + */ + public Date getRequestDate() { + return requestDate != null ? new Date(requestDate.getTime()) : null; + } + + /** + * @return The user that proposed the revision. Null if the revision was generated by an automated process. + */ + public @Nullable UserAccount getRequestedBy() { return requestedBy; } + + /** + * @return A flag signifying that the revision represents deletion of the device. + */ + public boolean isDeleted() { return deleted; } + + /** + * @return The section containing the device. + */ + public NamePart getSection() { return section; } + + /** + * @return The type of the device. + */ + public NamePart getDeviceType() { return deviceType; } + + /** + * @return An additional identifier that, in combination with other attributes, + * determine the unique convention name of the device. Null if omitted. + */ + public @Nullable String getInstanceIndex() { return instanceIndex; } + + /** + * @return The full name of the device in accordance with the naming convention. + */ + public String getConventionName() { return conventionName; } + + /** + * @return The representative of the equivalence class the convention name belongs to. + * This is used to ensure uniqueness of convention names when treating similar + * looking names (for example, containing 0 vs. O, 1 vs. l) as equal. + */ + public String getConventionNameEqClass() { return conventionNameEqClass; } + + /** + * Sets the convention equivalent class + * @param conventionNameEqClass the convention equivalent class to be set. + */ + public void setConventionNameEqClass(String conventionNameEqClass){ + this.conventionNameEqClass= conventionNameEqClass; + } + + /** + * @return Additional information (description, comment etc) about the device. + */ + public @Nullable String getAdditionalInfo(){ return additionalInfo;} + + @Nullable + public String getProcessorComment() { + return processorComment; + } + + public void setProcessorComment(@Nullable String processorComment) { + this.processorComment = processorComment; + } + + public boolean supersede(DeviceRevision other) { + // TODO: fix this...(when new revision are be created instead of updating old ones) + // return other == null + // || getId() > other.getId() + // || getId() == other.getId() && other.getStatus().isPending() && !getStatus().isPending(); + return other == null + || getId() > other.getId(); + } + +} diff --git a/src/main/java/org/openepics/names/old/model/NamePart.java b/src/main/java/org/openepics/names/old/model/NamePart.java new file mode 100644 index 0000000..b80c650 --- /dev/null +++ b/src/main/java/org/openepics/names/old/model/NamePart.java @@ -0,0 +1,88 @@ +/*- + * Copyright (c) 2014 European Spallation Source ERIC. + * Copyright (c) 2014 Cosylab d.d. + * + * This file is part of Naming Service. + * Naming Service is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or any newer version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt + */ + +package org.openepics.names.old.model; + +import com.google.common.base.Preconditions; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; + +import org.openepics.names.repository.model.Persistable; + +import java.util.Objects; +import java.util.UUID; + +/** + * An entity representing either a named section of the System Structure or a named device type of the Device Structure, + * depending on the specified namePartType. + * + * @author Marko Kolar + */ +@Entity +@Table(name = "namepart") +public class NamePart extends Persistable { + + /** + * + */ + private static final long serialVersionUID = 1674808840579429369L; + + private String uuid; + + @Column(name = "nameparttype") + @Enumerated(EnumType.STRING) + private NamePartType namePartType; + + protected NamePart() {} + + /** + * Constructs a new name part entity based on a UUID identifier and name part type. + * + * @param uuid the universally unique identifier + * @param namePartType the type of the NamePart + */ + public NamePart(UUID uuid, NamePartType namePartType) { + Preconditions.checkNotNull(uuid); + Preconditions.checkNotNull(namePartType); + this.uuid = uuid.toString(); + this.namePartType = namePartType; + } + + /** + * @return The universally unique identifier. + */ + public UUID getUuid() { return UUID.fromString(uuid); } + + /** + * @return The type of the NamePart. + */ + public NamePartType getNamePartType() { return namePartType; } + + @Override public boolean equals(Object other) { + return other instanceof NamePart && ((NamePart) other).getUuid().equals(getUuid()); + } + + @Override + public int hashCode() { + return Objects.hash(getUuid()); + } +} diff --git a/src/main/java/org/openepics/names/old/model/NamePartRevision.java b/src/main/java/org/openepics/names/old/model/NamePartRevision.java new file mode 100644 index 0000000..eea142c --- /dev/null +++ b/src/main/java/org/openepics/names/old/model/NamePartRevision.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2014 European Spallation Source ERIC. + * Copyright (c) 2014 Cosylab d.d. + * + * This software is Copyright by the Board of Trustees of Michigan + * State University (c) Copyright 2012. + * + * You may use this software under the terms of the GNU public license + * (GPL). The terms of this license are described at: + * http://www.gnu.org/licenses/gpl.txt + * + * Contact Information: + * Facilitty for Rare Isotope Beam + * Michigan State University + * East Lansing, MI 48824-1321 + * http://frib.msu.edu + */ + +package org.openepics.names.old.model; + +import com.google.common.base.Preconditions; + +import javax.annotation.Nullable; +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.Table; + +import org.openepics.names.repository.model.Persistable; + +import java.util.Date; + +/** + * A revision of a NamePart entity representing its state at some point in time. + * + * @author Vasu V + * @author Marko Kolar + * @author Karin Rathsman + */ +@Entity +@Table(name = "namepartrevision") +public class NamePartRevision extends Persistable { + + /** + * + */ + private static final long serialVersionUID = -8783968098755208875L; + + @JoinColumn(name = "namepart_id") + private @ManyToOne NamePart namePart; + + @Column(name = "requestdate") + private Date requestDate; + + @JoinColumn(name = "requestedby_id") + private @ManyToOne @Nullable UserAccount requestedBy; + + @Column(name = "requestercomment") + private @Nullable String requesterComment; + + private boolean deleted; + + @JoinColumn(name = "parent_id") + private @ManyToOne @Nullable NamePart parent; + + private String name; + + private @Nullable String mnemonic; + + private @Nullable String description=null; + + @Column(name = "mnemoniceqclass") + private @Nullable String mnemonicEqClass; + + @Enumerated(EnumType.STRING) + private NamePartRevisionStatus status; + + @JoinColumn(name = "processedby_id") + private @ManyToOne @Nullable UserAccount processedBy = null; + + @Column(name = "processdate") + private @Nullable Date processDate = null; + + @Column(name = "processorcomment") + private @Nullable String processorComment = null; + + protected NamePartRevision() {} + + /** + * Constructs a new name part revision entity (data object with relations). + * + * @param namePart the name part the revision pertains to + * @param requestDate the time when the revision was proposed + * @param requestedBy the user that proposed the revision. + * Null if the revision was generated by an automated process. + * @param requesterComment the comment the user gave when proposing the revision. Null if no comment was given. + * @param deleted a flag signifying that the revision represents deletion of the name part + * @param parent the parent of this name part in the hierarchy. Null if at the top of the hierarchy. + * @param name is the full name of the part. Does not need to follow a convention. + * @param mnemonic is the short, mnemonic name of the part in accordance with the naming convention + * @param description is the (lengthy) description or comment of the part. + * @param mnemonicEqClass the representative of the equivalence class the mnemonic belongs to. + * This is used to ensure uniqueness of mnemonics on certain levels when treating + * similar looking names (for example, containing 0 vs. O, 1 vs. l) as equal. + */ + public NamePartRevision( + NamePart namePart, Date requestDate, @Nullable UserAccount requestedBy, @Nullable String requesterComment, + boolean deleted, @Nullable NamePart parent, String name, String mnemonic, + @Nullable String description, String mnemonicEqClass) { + + Preconditions.checkNotNull(namePart); + Preconditions.checkNotNull(requestDate); + Preconditions.checkArgument(requesterComment == null || !requesterComment.isEmpty()); + Preconditions.checkArgument(name != null && !name.isEmpty()); + this.namePart = namePart; + this.requestDate = new Date(requestDate.getTime()); + this.requestedBy = requestedBy; + this.requesterComment = requesterComment; + this.deleted = deleted; + this.parent = parent; + this.name = name; + this.mnemonic = mnemonic; + this.description = description; + this.mnemonicEqClass = mnemonicEqClass; + this.status = NamePartRevisionStatus.PENDING; + } + + /** + * @return The name part the revision pertains to. + */ + public NamePart getNamePart() { return namePart; } + + /** + * Sets the status + * @param status the status to be set + */ + public void setStatus(NamePartRevisionStatus status){ + this.status = status; + } + + /** + * @return The time when the revision was proposed. + */ + public Date getRequestDate() { + return requestDate != null ? new Date(requestDate.getTime()) : null; + } + + /** + * @return The user that proposed the revision. Null if the revision was generated by an automated process. + */ + public @Nullable UserAccount getRequestedBy() { return requestedBy; } + + /** + * @return The comment the user gave when proposing the revision. Null if no comment was given. + */ + public @Nullable String getRequesterComment() { return requesterComment; } + + /** + * @return A flag signifying that the revision represents deletion of the name part. + */ + public boolean isDeleted() { return deleted; } + + /** + * @return The parent of this name part in the hierarchy. Null if at the top of the hierarchy. + */ + public @Nullable NamePart getParent() { return parent; } + + /** + * @return Full name of the part. Does not need to follow a convention. + */ + public String getName() { return name; } + + /** + * @return The short, mnemonic name of the part in accordance with the naming convention. + */ + public @Nullable String getMnemonic() { return mnemonic; } + + /** + * @return The description or other relevant information of the namepart. Optional. + */ + public @Nullable String getDescription(){ return description; } + + /** + * @return The representative of the equivalence class the mnemonic belongs to. + * This is used to ensure uniqueness of mnemonics on certain level when treating + * similar looking names (for example, containing 0 vs. O, 1 vs. l) as equal. + */ + public @Nullable String getMnemonicEqClass() { return mnemonicEqClass; } + + //TODO Remove after first deploy!!! + public void setMnemonicEqClass(String mnemonicEqClass) { this.mnemonicEqClass = mnemonicEqClass; } + + /** + * @return The status of the name part in the request / approve workflow. + */ + public NamePartRevisionStatus getStatus() { return status; } + + /** + * @return The time the revision was processed in the request / approve workflow. Null if still pending. + */ + public @Nullable Date getProcessDate() { + return processDate != null ? new Date(processDate.getTime()) : null; + } + + /** + * Sets the process date + * @param date the date to be set + */ + public void setProcessDate(Date date) { + this.processDate = date != null ? new Date(date.getTime()) : null; + } + + /** + * @return The user who processed the revision in the request / approve workflow. + * Null if still pending or if the revision @return was processed by an automated process. + */ + public @Nullable UserAccount getProcessedBy() { return processedBy; } + + /** + * Sets the process user + * @param user the process user + */ + public void setProcessedBy(UserAccount user){ + this.processedBy = user; + } + + /** + * @return The comment the user gave when processing the revision in the request / approve workflow. + * Null if still pending @return or if no comment was given. + */ + public @Nullable String getProcessorComment() { return processorComment; } + + /** + * Sets the processorComment + * @param message the message + */ + public void setProcessorComment(String message){ + this.processorComment = message; + } + + /** + * Updates the revision's status in the request / approve workflow as either approved, rejected or canceled. + * This completes the workflow and no further status changes are possible after that. + * + * @param status the new status of the name part + * @param date the time the revision was processed + * @param by the user who processed the revision. Null if the revision was processed by an automated process. + * @param comment the comment the administrator user gave when processing the revision. + * Null if no comment was given. + * + * @deprecated since version 3.3.2 + */ + @Deprecated + public void updateAsProcessed( + NamePartRevisionStatus status, Date date, @Nullable UserAccount by, @Nullable String comment) { + + Preconditions.checkState(this.status == NamePartRevisionStatus.PENDING); + Preconditions.checkArgument(status != NamePartRevisionStatus.PENDING); + Preconditions.checkNotNull(date); + Preconditions.checkArgument(comment == null || !comment.isEmpty()); + this.status = status; + this.processDate = new Date(date.getTime()); + this.processedBy = by; + this.processorComment = comment; + } + + /** + * rollback processed status to pending. + * + * @deprecated since version 3.3.2 + */ + @Deprecated + public void rollBackProcessed() { + Preconditions.checkState(!this.status.equals(NamePartRevisionStatus.PENDING),"Status cannot be Pending"); + this.status=NamePartRevisionStatus.PENDING; + this.processDate = null; + this.processedBy = null; + this.processorComment = null; + } + +} diff --git a/src/main/java/org/openepics/names/old/model/NamePartType.java b/src/main/java/org/openepics/names/old/model/NamePartType.java new file mode 100644 index 0000000..78a585f --- /dev/null +++ b/src/main/java/org/openepics/names/old/model/NamePartType.java @@ -0,0 +1,32 @@ +/*- + * Copyright (c) 2014 European Spallation Source ERIC. + * Copyright (c) 2014 Cosylab d.d. + * + * This file is part of Naming Service. + * Naming Service is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or any newer version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt + */ + +package org.openepics.names.old.model; + +/** + * Type of a NamePart specifying whether it belongs to the System Structure or the Device Structure. + * + * @author Marko Kolar + */ +public enum NamePartType { + /** A (sub)section of the Logical System Structure. */ + SECTION, + + /** A device (sub)type of the Device Category Structure. */ + DEVICE_TYPE +} diff --git a/src/main/java/org/openepics/names/old/model/Role.java b/src/main/java/org/openepics/names/old/model/Role.java new file mode 100644 index 0000000..36c1281 --- /dev/null +++ b/src/main/java/org/openepics/names/old/model/Role.java @@ -0,0 +1,38 @@ +/*- + * Copyright (c) 2014 European Spallation Source ERIC. + * Copyright (c) 2014 Cosylab d.d. + * + * This file is part of Naming Service. + * Naming Service is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or any newer version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt + */ + +package org.openepics.names.old.model; + +/** + * UserAccount's role that determines the user's access control permissions. Used by the SessionServiceTest (not RBAC) + * + * @author Marko Kolar + */ +public enum Role { + /** + * Editor role giving permissions to propose changes to the Logical System and Device Category structures + * and to add, modify or delete Devices. + */ + EDITOR, + + /** + * Super user role giving permission to approve or reject proposed changes to System and Device structures, + * in addition to normal Editor permissions. + */ + SUPERUSER +} diff --git a/src/main/java/org/openepics/names/old/model/UserAccount.java b/src/main/java/org/openepics/names/old/model/UserAccount.java new file mode 100644 index 0000000..739f460 --- /dev/null +++ b/src/main/java/org/openepics/names/old/model/UserAccount.java @@ -0,0 +1,87 @@ +/*- + * Copyright (c) 2014 European Spallation Source ERIC. + * Copyright (c) 2014 Cosylab d.d. + * + * This file is part of Naming Service. + * Naming Service is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free + * Software Foundation, either version 2 of the License, or any newer version. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see https://www.gnu.org/licenses/gpl-2.0.txt + */ + +package org.openepics.names.old.model; + +import com.google.common.base.Preconditions; + +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; + +import org.openepics.names.repository.model.Persistable; + +import java.util.Objects; + +/** + * An entity representing a user account used to sing in to the application. + * + * @author Vasu V + * @author Marko Kolar + */ +@Entity +@Table(name = "useraccount") +public class UserAccount extends Persistable { + + /** + * + */ + private static final long serialVersionUID = -6026728360254460697L; + + private String username; + + @Enumerated(EnumType.STRING) private Role role; + + protected UserAccount() {} + + /** + * Constructs a new user account object given username and role. + * + * @param username the name identifying the user + * @param role the role that determines the user's access control permissions + */ + public UserAccount(String username, Role role) { + Preconditions.checkArgument(username != null && !username.isEmpty()); + Preconditions.checkNotNull(role); + this.username = username; + this.role = role; + } + + /** + * @return The name identifying the user + */ + public String getUsername() { return username; } + + /** + * @return The role that determines the user's access control permissions + */ + public Role getRole() { return role; } + + @Override public int hashCode() { + return Objects.hashCode(username); + } + + @Override public boolean equals(Object other) { + return other instanceof UserAccount && ((UserAccount) other).getUsername().equals(getUsername()); + } + + @Override public String toString(){ + return getUsername(); + } +} diff --git a/src/main/java/org/openepics/names/old/nameviews/NameViewProvider.java b/src/main/java/org/openepics/names/old/nameviews/NameViewProvider.java new file mode 100644 index 0000000..da8a0b1 --- /dev/null +++ b/src/main/java/org/openepics/names/old/nameviews/NameViewProvider.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) 2016 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.openepics.names.old.nameviews; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.openepics.names.old.model.DeviceRevision; + +/** + * Implementation of part of NameViewProvider. + * Been that generates the nameView tree and updates name views. + * + * @author karinrathsman + * @author Lars Johansson + * + */ +public class NameViewProvider implements Serializable { + + /** + * + */ + private static final long serialVersionUID = 6916728601880286405L; + + private NameRevisions nameRevisions; + + public NameViewProvider() { + nameRevisions = new NameRevisions(); + } + + public void update(List<DeviceRevision> newRevisions) { + for (DeviceRevision revision : newRevisions) { + nameRevisions.update(revision); + } + } + + public String equivalenceClassRepresentative(String name) { + return name != null + ? name.toUpperCase() + .replaceAll("(?<=[A-Za-z])0+", "") + .replace('I', '1').replace('L', '1').replace('O', '0') + .replaceAll("(?<!\\d)0+(?=\\d)", "") + : null; + } + + /** + * @return the restfulNameRevisionMap that maps a given device name with the + * latest revision with that name. + */ + public NameRevisions getNameRevisions() { + return nameRevisions; + } + + /** + * This class handles map of key-value with equivalence class representative of convention name as key + * and name revision as value. It is used to build structure of name data. + * + * <p> + * Equivalence class representative of convention name is used as key as it ensure uniqueness of names + * when treating similar looking names. Name revision represents database entry. + */ + public class NameRevisions { + + private Map<String, DeviceRevision> deviceRevisionMap; + + /** + * Constructs new map for equivalence class representative of convention name and name revision objects. + */ + public NameRevisions() { + deviceRevisionMap = new HashMap<>(); + } + + private String key(String name) { + return equivalenceClassRepresentative(name); + } + + /** + * Retrieves content from nameRevisionMap for given parameter as <tt>uuid</tt> or <tt>name</tt>. + * Among usage is REST API and its methods. + * + * @param string uuid or name + * @return name revision + */ + public DeviceRevision get(String string) { + // note + // equivalenceClassRepresentative of name as key in deviceRevisionMap + // + // method not to call key method with input string directly since that would mean + // calling equivalenceClassRepresentative twice, which would potentially strip + // information from input string, e.g. CRY0 would become CRY + // + // method works with + // - name + // - equivalenceClassRepresentative of name + // - uuid + // + // 1. consider input equivalenceClassRepresentative + // 2. consider input name (make equivalenceClassRepresentative) + // 3. consider input uuid + + String key = string; + if (deviceRevisionMap.containsKey(key)) { + return deviceRevisionMap.get(key); + } else if (deviceRevisionMap.containsKey(key(key))) { + return deviceRevisionMap.get(key(key)); + } else { + return null; + } + } + + /** + * Updates the deviceRevisionMap if the specified revision supersedes the + * existing revision with the same name + * + * @param revision + */ + protected void update(DeviceRevision revision) { + String key = key(revision.getConventionName()); + DeviceRevision otherRevision = deviceRevisionMap.get(key); + if (revision.supersede(otherRevision)) { + deviceRevisionMap.put(key, revision); + } + } + + /** + * Return key set for device revision map. + * + * @return key set for device revision map + * + * @see NameRevisions + */ + public Set<String> keySet() { + return deviceRevisionMap.keySet(); + } + + /** + * Return entry set for device revision map. + * + * @return entry set for device revision map + * + * @see NameRevisions + */ + public Set<Map.Entry<String, DeviceRevision>> entrySet() { + return deviceRevisionMap.entrySet(); + } + + /** + * Return if device revision map contains given key. + * + * @param name key for device revision map + * @return if device revision map contains given key + * + * @see NameRevisions + */ + public boolean contains(String name) { + return deviceRevisionMap.containsKey(key(name)); + } + + } + +} diff --git a/src/main/java/org/openepics/names/repository/old/IDeviceRevisionRepository.java b/src/main/java/org/openepics/names/repository/old/IDeviceRevisionRepository.java new file mode 100644 index 0000000..c31c35e --- /dev/null +++ b/src/main/java/org/openepics/names/repository/old/IDeviceRevisionRepository.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.openepics.names.repository.old; + +import org.openepics.names.old.model.DeviceRevision; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * Find device revision information from JPA. + * + * @author Lars Johansson + */ +@Repository +public interface IDeviceRevisionRepository extends JpaRepository<DeviceRevision, Long> { +} diff --git a/src/main/java/org/openepics/names/repository/old/INamePartRevisionRepository.java b/src/main/java/org/openepics/names/repository/old/INamePartRevisionRepository.java new file mode 100644 index 0000000..c26b7d1 --- /dev/null +++ b/src/main/java/org/openepics/names/repository/old/INamePartRevisionRepository.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2021 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.openepics.names.repository.old; + +import org.openepics.names.old.model.NamePartRevision; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +/** + * Find name part revision information from JPA. + * + * @author Lars Johansson + */ +@Repository +public interface INamePartRevisionRepository extends JpaRepository<NamePartRevision, Long> { +} diff --git a/src/main/java/org/openepics/names/rest/controller/VerificationController.java b/src/main/java/org/openepics/names/rest/controller/VerificationController.java new file mode 100644 index 0000000..1e0f691 --- /dev/null +++ b/src/main/java/org/openepics/names/rest/controller/VerificationController.java @@ -0,0 +1,1045 @@ +/* + * Copyright (C) 2021 European Spallation Source ERIC. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +package org.openepics.names.rest.controller; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.UUID; +import java.util.logging.Level; +import java.util.logging.Logger; + +import org.apache.commons.lang3.StringUtils; +import org.openepics.names.old.model.DeviceRevision; +import org.openepics.names.old.model.NamePartRevision; +import org.openepics.names.old.model.NamePartRevisionStatus; +import org.openepics.names.old.nameviews.NameViewProvider; +import org.openepics.names.repository.IDeviceGroupRepository; +import org.openepics.names.repository.IDeviceTypeRepository; +import org.openepics.names.repository.IDisciplineRepository; +import org.openepics.names.repository.INameRepository; +import org.openepics.names.repository.ISubsystemRepository; +import org.openepics.names.repository.ISystemGroupRepository; +import org.openepics.names.repository.ISystemRepository; +import org.openepics.names.repository.model.DeviceGroup; +import org.openepics.names.repository.model.DeviceType; +import org.openepics.names.repository.model.Discipline; +import org.openepics.names.repository.model.Name; +import org.openepics.names.repository.model.Subsystem; +import org.openepics.names.repository.model.System; +import org.openepics.names.repository.model.SystemGroup; +import org.openepics.names.repository.old.IDeviceRevisionRepository; +import org.openepics.names.repository.old.INamePartRevisionRepository; +import org.openepics.names.util.HolderIRepositories; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.EnableAutoConfiguration; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * This part of REST API provides verification of data migration for Naming application. + * + * <p>Prerequisite(s) + * <ul> + * <li>both old and migrated database available</li> + * </ul> + * + * <p> + * Ideally, some knowledge of database tables and object structures acquired to dig into this class. + * It can be done in any case and there is documentation available. + * It is recommended to have database tables and object structures available. + * + * @author Lars Johansson + */ +@RestController +@RequestMapping("/verification") +@EnableAutoConfiguration +public class VerificationController { + + // note + // global exception handler available + + /* + Methods + read GET /verification/migration_devicerevision - readMigrationDeviceRevision() + read GET /verification/migration_namepartrevision - readMigrationNamePartRevision + read GET /verification/data_reachable - readDataReachable() + read GET /verification/restapi_oldvsnew - readRestApiOldVsNew() + */ + + private static final Logger LOGGER = Logger.getLogger(VerificationController.class.getName()); + + private static final String NEW_LINE_BR = "<br/>"; + private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); + + private HolderIRepositories holderIRepositories; + + @Autowired + IDeviceRevisionRepository deviceRevisionRepository; + @Autowired + INamePartRevisionRepository namePartRevisionRepository; + + @Autowired + public VerificationController( + INameRepository nameRepository, + ISystemGroupRepository systemGroupRepository, + ISystemRepository systemRepository, + ISubsystemRepository subsystemRepository, + IDisciplineRepository disciplineRepository, + IDeviceGroupRepository deviceGroupRepository, + IDeviceTypeRepository deviceTypeRepository) { + + holderIRepositories = new HolderIRepositories( + nameRepository, + systemGroupRepository, + systemRepository, + subsystemRepository, + disciplineRepository, + deviceGroupRepository, + deviceTypeRepository); + } + + /** + * Perform verification of data migration with focus on device revision. + * Ok if all entries ok and no entry nok. + * + * @return report of data migration + */ + @GetMapping ("/migration_devicerevision") + public String readMigrationDeviceRevision() { + // verification of + // name vs. devicerevision, device + // with help of namepartrevision, namepart + + // note + // check entry by entry + // ---------- + // to check 1st, 2nd, 3rd parent to determine if systemgroup, system or subsystem + // otherwise not clear how to make sure if it is supposed to be systemgroup and not system, subsystem and vice versa + // ---------- + // date may be in different format for different objects, to be formatted before being compared + // ---------- + // name.id = devicerevision.id + // name.version = devicerevision.version + // name.uuid = devicerevision.device_id (device.id --> device.uuid) + // name.namepartrevision_systemgroup_uuid = devicerevision.section_id (namepart.id --> namepart.uuid + // name.namepartrevision_system_uuid = devicerevision.section_id (namepart.id --> namepart.uuid + // name.namepartrevision_subsystem_uuid = devicerevision.section_id (namepart.id --> namepart.uuid + // name.namepartrevision_devicetype_uuid = devicerevision.devicetype_id (namepart.id --> namepart.uuid + // name.instance_index = devicerevision.instanceindex + // name.convention_name = devicerevision.conventionname + // name.convention_name_equivalence = devicerevision.conventionnameeqclass + // name.description = devicerevision.additionalinfo + // name.status = null + // name.latest = true if id = get max id for uuid (not consider status) + // name.deleted = devicerevision.deleted + // name.requested = devicerevision.requestdate + // name.requested_by = devicerevision.requestedby_id (useraccount.id --> useraccount.username) + // name.requested_comment = null + // name.processed = null + // name.processed_by = null + // name.processed_comment = devicerevision.processorcomment + + StringBuilder reportHtml = new StringBuilder(); + + // find data + // for verification + // names + // to support + // device names + // name part revisions + + List<Name> names = holderIRepositories.getNameRepository().findAll(); + List<DeviceRevision> deviceNames = deviceRevisionRepository.findAll(); + List<NamePartRevision> namePartRevisions = namePartRevisionRepository.findAll(); + + prepareLogReport("readMigrationDeviceRevision, find data, names.size: " + names.size(), reportHtml); + prepareLogReport("readMigrationDeviceRevision, find data, deviceNames.size: " + deviceNames.size(), reportHtml); + prepareLogReport("readMigrationDeviceRevision, find data, namePartRevisions.size: " + namePartRevisions.size(), reportHtml); + + // utility + // interpret lists into hashmaps for faster retrieval in for loop below + // used to help check name entries + // ---------- + // mapIdDeviceRevision - find corresponding (old) device revision for given id + // mapUuidMaxIdName - find out latest name id for given uuid + // mapUuidNamePartRevision - find out name part revision for given uuid + + HashMap<Long, DeviceRevision> mapIdDeviceRevision = new HashMap<>((int)(deviceNames.size()/0.75 + 2)); + for (DeviceRevision deviceRevision : deviceNames) { + mapIdDeviceRevision.put(deviceRevision.getId(), deviceRevision); + } + HashMap<UUID, Long> mapUuidMaxIdName = new HashMap<>(); + for (Name name : names) { + if (mapUuidMaxIdName.get(name.getUuid()) == null + || name.getId() > mapUuidMaxIdName.get(name.getUuid())) { + mapUuidMaxIdName.put(name.getUuid(), name.getId()); + } + } + HashMap<UUID, NamePartRevision> mapUuidNamePartRevision = new HashMap<>((int)(namePartRevisions.size()/0.75 + 2)); + for (NamePartRevision namePartRevision : namePartRevisions) { + mapUuidNamePartRevision.put(namePartRevision.getNamePart().getUuid(), namePartRevision); + } + + prepareLogReport("readMigrationDeviceRevision, utility, mapIdDeviceRevision.size: " + mapIdDeviceRevision.size(), reportHtml); + prepareLogReport("readMigrationDeviceRevision, utility, mapUuidMaxIdName.size: " + mapUuidMaxIdName.size(), reportHtml); + prepareLogReport("readMigrationDeviceRevision, utility, mapUuidNamePartRevision.size: " + mapUuidNamePartRevision.size(), reportHtml); + + // keep track of id + // ok + // nok + SortedSet<Long> id_ok_deviceRevisionDevice = new TreeSet<>(); + SortedSet<Long> id_nok_deviceRevisionDevice = new TreeSet<>(); + + prepareLogReport("readMigrationDeviceRevision, check, before, id_ok_deviceRevisionDevice.size: " + id_ok_deviceRevisionDevice.size(), reportHtml); + prepareLogReport("readMigrationDeviceRevision, check, before, id_nok_deviceRevisionDevice.size: " + id_nok_deviceRevisionDevice.size(), reportHtml); + + // name + // check entry by entry + // each attribute as expected + boolean check = false; + DeviceRevision deviceRevision = null; + for (Name name : names) { + check = true; + deviceRevision = mapIdDeviceRevision.get(name.getId()); + + check = deviceRevision != null; + + check = check && name.getId().equals(deviceRevision.getId()); + check = check && name.getVersion().equals(deviceRevision.getVersion()); + check = check && name.getUuid().equals(deviceRevision.getDevice().getUuid()); + + // to check 1st, 2nd, 3rd parent to determine if systemgroup, system or subsystem + // otherwise not clear how to make sure if it is supposed to be systemgroup and not system, subsystem and vice versa + NamePartRevision parent1 = deviceRevision.getSection() != null + ? mapUuidNamePartRevision.get(deviceRevision.getSection().getUuid()) + : null; + NamePartRevision parent2 = parent1 != null && parent1.getParent() != null + ? mapUuidNamePartRevision.get(parent1.getParent().getUuid()) + : null; + NamePartRevision parent3 = parent2 != null && parent2.getParent() != null + ? mapUuidNamePartRevision.get(parent2.getParent().getUuid()) + : null; + // parent 1 but not parent 2, 3 - system group + // parent 1, 2 but not parent 3 - system + // parent 1, 2, 3 - subsystem + // else nok + UUID systemGroupUuid = name.getSystemgroupUuid(); + UUID systemUuid = name.getSystemUuid(); + UUID subsystemUuid = name.getSubsystemUuid(); + UUID sectionUuid = deviceRevision.getSection().getUuid(); + if (parent1 != null && parent2 == null && parent3 == null) { + check = check && sectionUuid.equals(systemGroupUuid) && systemUuid == null && subsystemUuid == null; + } else if (parent1 != null && parent2 != null && parent3 == null) { + check = check && sectionUuid.equals(systemUuid) && systemGroupUuid == null && subsystemUuid == null; + } else if (parent1 != null && parent2 != null && parent3 != null) { + check = check && sectionUuid.equals(subsystemUuid) && systemGroupUuid == null && systemUuid == null; + } else { + check = false; + } + + check = check && ((name.getDevicetypeUuid() == null && deviceRevision.getDeviceType() == null) + || (name.getDevicetypeUuid().equals(deviceRevision.getDeviceType().getUuid()))); + check = check && StringUtils.equals(name.getInstanceIndex(), deviceRevision.getInstanceIndex()); + check = check && StringUtils.equals(name.getConventionName(), deviceRevision.getConventionName()); + check = check && StringUtils.equals(name.getConventionNameEquivalence(), deviceRevision.getConventionNameEqClass()); + check = check && StringUtils.equals(name.getDescription(), deviceRevision.getAdditionalInfo()); + check = check && name.getStatus() == null; + + // latest + // true if id = get max id for uuid + check = check && name.isLatest() == name.getId().equals(mapUuidMaxIdName.get(name.getUuid())); + check = check && name.isDeleted() == deviceRevision.isDeleted(); + + // date may be in different format for different objects, to be formatted before being compared + check = check && ((name.getRequested() == null && deviceRevision.getRequestDate() == null) + || StringUtils.equals(SDF.format(name.getRequested()), SDF.format(deviceRevision.getRequestDate()))); + check = check && StringUtils.equals(name.getRequestedBy(), deviceRevision.getRequestedBy().getUsername()); + check = check && name.getRequestedComment() == null; + check = check && name.getProcessed() == null; + check = check && name.getProcessedBy() == null; + check = check && StringUtils.equals(name.getProcessedComment(), deviceRevision.getProcessorComment()); + + // add to count + if (check) { + id_ok_deviceRevisionDevice.add(name.getId()); + } else { + id_nok_deviceRevisionDevice.add(name.getId()); + } + } + + // ok if + // all entries ok + // no entry nok + boolean ok = id_ok_deviceRevisionDevice.size() == names.size() + && id_nok_deviceRevisionDevice.size() == 0; + + prepareLogReport("readMigrationDeviceRevision, check, after, id_ok_deviceRevisionDevice.size: " + id_ok_deviceRevisionDevice.size(), reportHtml); + prepareLogReport("readMigrationDeviceRevision, check, after, id_nok_deviceRevisionDevice.size: " + id_nok_deviceRevisionDevice.size(), reportHtml); + prepareLogReport("readMigrationDeviceRevision, ok: " + ok, reportHtml); + + return reportHtml.toString(); + } + + /** + * Perform verification of data migration with focus on name part revision. + * Ok if all entries ok and no entry nok. + * + * @return report of data migration + */ + @GetMapping ("/migration_namepartrevision") + public String readMigrationNamePartRevision() { + // verification of + // systemgroup + // system + // subsystem + // discipline + // devicegroup + // devicetype + // vs. + // namepartrevision + // with help of namepartrevision, namepart + + // note + // check entry by entry + // ---------- + // to check parent uuid + // ---------- + // date may be in different format for different objects, to be formatted before being compared + // ---------- + // e.g. + // system.id = namepartrevision.id + // system.version = namepartrevision.version + // system.uuid = namepartrevision.namepart_id (namepart.id --> namepart.uuid) + // ( system.parent_uuid = namepartrevision.parent_id (namepart.id --> namepart.uuid) ) + // system.name = namepartrevision.name + // system.mnemonic = namepartrevision.mnemonic + // system.mnemonicequivalence = namepartrevision.mnemoniceqclass + // system.description = namepartrevision.description + // system.status = namepartrevision.status + // system.latest = true if id = get max id for uuid (consider status, but not PENDING) + // system.deleted = namepartrevision.deleted + // system.requested = namepartrevision.requestdate + // system.requested_by = namepartrevision.requestedby_id (useraccount.id --> useraccount.username) + // system.requested_comment = namepartrevision.requestercomment + // system.processed = namepartrevision.processdate + // system.processed_by = namepartrevision.processedby_id (useraccount.id --> useraccount.username) + // system.processed_comment = namepartrevision.processorcomment + + StringBuilder reportHtml = new StringBuilder(); + + // find data + // for verification + // system groups + // systems + // subsystems + // disciplines + // device groups + // device types + // to support + // name part revisions + + List<SystemGroup> systemGroups = holderIRepositories.getSystemGroupRepository().findAll(); + List<System> systems = holderIRepositories.getSystemRepository().findAll(); + List<Subsystem> subsystems = holderIRepositories.getSubsystemRepository().findAll(); + List<Discipline> disciplines = holderIRepositories.getDisciplineRepository().findAll(); + List<DeviceGroup> deviceGroups = holderIRepositories.getDeviceGroupRepository().findAll(); + List<DeviceType> deviceTypes = holderIRepositories.getDeviceTypeRepository().findAll(); + List<NamePartRevision> namePartRevisions = namePartRevisionRepository.findAll(); + + prepareLogReport("readMigrationNamePartRevision, find data, systemGroups.size: " + systemGroups.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, find data, systems.size: " + systems.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, find data, subsystems.size: " + subsystems.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, find data, disciplines.size: " + disciplines.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, find data, deviceGroups.size: " + deviceGroups.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, find data, deviceTypes.size: " + deviceTypes.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, find data, namePartRevisions.size: " + namePartRevisions.size(), reportHtml); + + // utility + // interpret lists into hashmaps for faster retrieval in for loop below + // used to help check name entries + // ---------- + // mapIdNamePartRevision - find corresponding (old) name part revision for given id + // mapUuidMaxIdSystemGroup - find out latest system group id for given uuid + // mapUuidMaxIdSystem - find out latest system id for given uuid + // mapUuidMaxIdSubsystem - find out latest subsystem id for given uuid + // mapUuidMaxIdDiscipline - find out latest discipline id for given uuid + // mapUuidMaxIdDeviceGroup - find out latest device group id for given uuid + // mapUuidMaxIdDeviceType - find out latest device type id for given uuid + + HashMap<Long, NamePartRevision> mapIdNamePartRevision = new HashMap<>((int)(namePartRevisions.size()/0.75 + 2)); + for (NamePartRevision namePartRevision : namePartRevisions) { + mapIdNamePartRevision.put(namePartRevision.getId(), namePartRevision); + } + HashMap<UUID, Long> mapUuidMaxIdSystemGroup = new HashMap<>(); + for (SystemGroup systemGroup : systemGroups) { + if ( (mapUuidMaxIdSystemGroup.get(systemGroup.getUuid()) == null + || systemGroup.getId() > mapUuidMaxIdSystemGroup.get(systemGroup.getUuid())) + && NamePartRevisionStatus.APPROVED.name().equals(systemGroup.getStatus().name())) { + mapUuidMaxIdSystemGroup.put(systemGroup.getUuid(), systemGroup.getId()); + } + } + HashMap<UUID, Long> mapUuidMaxIdSystem = new HashMap<>(); + for (System system : systems) { + if (( mapUuidMaxIdSystem.get(system.getUuid()) == null + || system.getId() > mapUuidMaxIdSystem.get(system.getUuid())) + && NamePartRevisionStatus.APPROVED.name().equals(system.getStatus().name())) { + mapUuidMaxIdSystem.put(system.getUuid(), system.getId()); + } + } + HashMap<UUID, Long> mapUuidMaxIdSubsystem = new HashMap<>(); + for (Subsystem subsystem : subsystems) { + if ( (mapUuidMaxIdSubsystem.get(subsystem.getUuid()) == null + || subsystem.getId() > mapUuidMaxIdSubsystem.get(subsystem.getUuid())) + && NamePartRevisionStatus.APPROVED.name().equals(subsystem.getStatus().name())) { + mapUuidMaxIdSubsystem.put(subsystem.getUuid(), subsystem.getId()); + } + } + HashMap<UUID, Long> mapUuidMaxIdDiscipline = new HashMap<>(); + for (Discipline discipline : disciplines) { + if ((mapUuidMaxIdDiscipline.get(discipline.getUuid()) == null + || discipline.getId() > mapUuidMaxIdDiscipline.get(discipline.getUuid())) + && NamePartRevisionStatus.APPROVED.name().equals(discipline.getStatus().name())) { + mapUuidMaxIdDiscipline.put(discipline.getUuid(), discipline.getId()); + } + } + HashMap<UUID, Long> mapUuidMaxIdDeviceGroup = new HashMap<>(); + for (DeviceGroup deviceGroup : deviceGroups) { + if ((mapUuidMaxIdDeviceGroup.get(deviceGroup.getUuid()) == null + || deviceGroup.getId() > mapUuidMaxIdDeviceGroup.get(deviceGroup.getUuid())) + && NamePartRevisionStatus.APPROVED.name().equals(deviceGroup.getStatus().name())) { + mapUuidMaxIdDeviceGroup.put(deviceGroup.getUuid(), deviceGroup.getId()); + } + } + HashMap<UUID, Long> mapUuidMaxIdDeviceType = new HashMap<>(); + for (DeviceType deviceType : deviceTypes) { + if ((mapUuidMaxIdDeviceType.get(deviceType.getUuid()) == null + || deviceType.getId() > mapUuidMaxIdDeviceType.get(deviceType.getUuid())) + && NamePartRevisionStatus.APPROVED.name().equals(deviceType.getStatus().name())) { + mapUuidMaxIdDeviceType.put(deviceType.getUuid(), deviceType.getId()); + } + } + + prepareLogReport("readMigrationNamePartRevision, utility, mapIdNamePartRevision.size: " + mapIdNamePartRevision.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, utility, mapUuidMaxIdSystemGroup.size: " + mapUuidMaxIdSystemGroup.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, utility, mapUuidMaxIdSystem.size: " + mapUuidMaxIdSystem.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, utility, mapUuidMaxIdSubsystem.size: " + mapUuidMaxIdSubsystem.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, utility, mapUuidMaxIdDiscipline.size: " + mapUuidMaxIdDiscipline.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, utility, mapUuidMaxIdDeviceGroup.size: " + mapUuidMaxIdDeviceGroup.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, utility, mapUuidMaxIdDeviceType.size: " + mapUuidMaxIdDeviceType.size(), reportHtml); + + // keep track of id + // ok + // nok + SortedSet<Long> id_ok_namePartRevision = new TreeSet<>(); + SortedSet<Long> id_nok_namePartRevision = new TreeSet<>(); + + prepareLogReport("readMigrationNamePartRevision, check, before, id_ok_namePartRevision.size: " + id_ok_namePartRevision.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, check, before, id_nok_namePartRevision.size: " + id_nok_namePartRevision.size(), reportHtml); + + // system group + // check entry by entry + // each attribute as expected + boolean check = false; + NamePartRevision namePartRevision = null; + for (SystemGroup systemGroup : systemGroups) { + check = true; + namePartRevision = mapIdNamePartRevision.get(systemGroup.getId()); + + check = namePartRevision != null; + + check = check && systemGroup.getId().equals(namePartRevision.getId()); + check = check && systemGroup.getVersion().equals(namePartRevision.getVersion()); + check = check && systemGroup.getUuid().equals(namePartRevision.getNamePart().getUuid()); + + // no parent uuid for system group + check = check && StringUtils.equals(systemGroup.getName(), namePartRevision.getName()); + check = check && StringUtils.equals(systemGroup.getMnemonic(), namePartRevision.getMnemonic()); + check = check && StringUtils.equals(systemGroup.getMnemonicEquivalence(), namePartRevision.getMnemonicEqClass()); + check = check && StringUtils.equals(systemGroup.getDescription(), namePartRevision.getDescription()); + check = check && ((systemGroup.getStatus() == null && namePartRevision.getStatus() == null) + || (systemGroup.getStatus().name().equals(namePartRevision.getStatus().name()))); + + // latest + // true if id = get max id for uuid + // special rules for pending, not consider pending + check = check && systemGroup.isLatest() == systemGroup.getId().equals(mapUuidMaxIdSystemGroup.get(systemGroup.getUuid())); + check = check && systemGroup.isDeleted() == namePartRevision.isDeleted(); + + // date may be in different format for different objects, to be formatted before being compared + check = check && ((systemGroup.getRequested() == null && namePartRevision.getRequestDate() == null) + || StringUtils.equals(SDF.format(systemGroup.getRequested()), SDF.format(namePartRevision.getRequestDate()))); + check = check && (systemGroup.getRequestedBy() == null && namePartRevision.getRequestedBy() == null + || StringUtils.equals(systemGroup.getRequestedBy(), namePartRevision.getRequestedBy().getUsername())); + check = check && StringUtils.equals(systemGroup.getRequestedComment(), namePartRevision.getRequesterComment()); + check = check && ((systemGroup.getProcessed() == null && namePartRevision.getProcessDate() == null) + || StringUtils.equals(SDF.format(systemGroup.getProcessed()), SDF.format(namePartRevision.getProcessDate()))); + check = check && (systemGroup.getProcessedBy() == null && namePartRevision.getProcessedBy() == null + || StringUtils.equals(systemGroup.getProcessedBy(), namePartRevision.getProcessedBy().getUsername())); + check = check && StringUtils.equals(systemGroup.getProcessedComment(), namePartRevision.getProcessorComment()); + + // add to count + if (check) { + id_ok_namePartRevision.add(systemGroup.getId()); + } else { + id_nok_namePartRevision.add(systemGroup.getId()); + } + } + prepareLogReport("readMigrationNamePartRevision, check, after systemgroup, id_ok_namePartRevision.size: " + id_ok_namePartRevision.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, check, after systemgroup, id_nok_namePartRevision.size: " + id_nok_namePartRevision.size(), reportHtml); + + // system + // check entry by entry + // each attribute as expected + for (System system : systems) { + check = true; + namePartRevision = mapIdNamePartRevision.get(system.getId()); + + check = namePartRevision != null; + + check = check && system.getId().equals(namePartRevision.getId()); + check = check && system.getVersion().equals(namePartRevision.getVersion()); + check = check && system.getUuid().equals(namePartRevision.getNamePart().getUuid()); + + // parent uuid + check = check && system.getParentUuid().equals(namePartRevision.getParent().getUuid()); + check = check && StringUtils.equals(system.getName(), namePartRevision.getName()); + check = check && StringUtils.equals(system.getMnemonic(), namePartRevision.getMnemonic()); + check = check && StringUtils.equals(system.getMnemonicEquivalence(), namePartRevision.getMnemonicEqClass()); + check = check && StringUtils.equals(system.getDescription(), namePartRevision.getDescription()); + check = check && ((system.getStatus() == null && namePartRevision.getStatus() == null) + || (system.getStatus().name().equals(namePartRevision.getStatus().name()))); + + // latest + // true if id = get max id for uuid + // special rules for pending, not consider pending + check = check && system.isLatest() == system.getId().equals(mapUuidMaxIdSystem.get(system.getUuid())); + check = check && system.isDeleted() == namePartRevision.isDeleted(); + + // date may be in different format for different objects, to be formatted before being compared + check = check && ((system.getRequested() == null && namePartRevision.getRequestDate() == null) + || StringUtils.equals(SDF.format(system.getRequested()), SDF.format(namePartRevision.getRequestDate()))); + check = check && (system.getRequestedBy() == null && namePartRevision.getRequestedBy() == null + || StringUtils.equals(system.getRequestedBy(), namePartRevision.getRequestedBy().getUsername())); + check = check && StringUtils.equals(system.getRequestedComment(), namePartRevision.getRequesterComment()); + check = check && ((system.getProcessed() == null && namePartRevision.getProcessDate() == null) + || StringUtils.equals(SDF.format(system.getProcessed()), SDF.format(namePartRevision.getProcessDate()))); + check = check && (system.getProcessedBy() == null && namePartRevision.getProcessedBy() == null + || StringUtils.equals(system.getProcessedBy(), namePartRevision.getProcessedBy().getUsername())); + check = check && StringUtils.equals(system.getProcessedComment(), namePartRevision.getProcessorComment()); + + // add to count + if (check) { + id_ok_namePartRevision.add(system.getId()); + } else { + id_nok_namePartRevision.add(system.getId()); + } + } + prepareLogReport("readMigrationNamePartRevision, check, after system, id_ok_namePartRevision.size: " + id_ok_namePartRevision.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, check, after system, id_nok_namePartRevision.size: " + id_nok_namePartRevision.size(), reportHtml); + + // subsystem + // check entry by entry + // each attribute as expected + for (Subsystem subsystem : subsystems) { + check = true; + namePartRevision = mapIdNamePartRevision.get(subsystem.getId()); + + check = namePartRevision != null; + + check = check && subsystem.getId().equals(namePartRevision.getId()); + check = check && subsystem.getVersion().equals(namePartRevision.getVersion()); + check = check && subsystem.getUuid().equals(namePartRevision.getNamePart().getUuid()); + + // parent uuid + check = check && subsystem.getParentUuid().equals(namePartRevision.getParent().getUuid()); + check = check && StringUtils.equals(subsystem.getName(), namePartRevision.getName()); + check = check && StringUtils.equals(subsystem.getMnemonic(), namePartRevision.getMnemonic()); + check = check && StringUtils.equals(subsystem.getMnemonicEquivalence(), namePartRevision.getMnemonicEqClass()); + check = check && StringUtils.equals(subsystem.getDescription(), namePartRevision.getDescription()); + check = check && ((subsystem.getStatus() == null && namePartRevision.getStatus() == null) + || (subsystem.getStatus().name().equals(namePartRevision.getStatus().name()))); + + // latest + // true if id = get max id for uuid + // special rules for pending, not consider pending + check = check && subsystem.isLatest() == subsystem.getId().equals(mapUuidMaxIdSubsystem.get(subsystem.getUuid())); + check = check && subsystem.isDeleted() == namePartRevision.isDeleted(); + + // date may be in different format for different objects, to be formatted before being compared + check = check && ((subsystem.getRequested() == null && namePartRevision.getRequestDate() == null) + || StringUtils.equals(SDF.format(subsystem.getRequested()), SDF.format(namePartRevision.getRequestDate()))); + check = check && (subsystem.getRequestedBy() == null && namePartRevision.getRequestedBy() == null + || StringUtils.equals(subsystem.getRequestedBy(), namePartRevision.getRequestedBy().getUsername())); + check = check && StringUtils.equals(subsystem.getRequestedComment(), namePartRevision.getRequesterComment()); + check = check && ((subsystem.getProcessed() == null && namePartRevision.getProcessDate() == null) + || StringUtils.equals(SDF.format(subsystem.getProcessed()), SDF.format(namePartRevision.getProcessDate()))); + check = check && (subsystem.getProcessedBy() == null && namePartRevision.getProcessedBy() == null + || StringUtils.equals(subsystem.getProcessedBy(), namePartRevision.getProcessedBy().getUsername())); + check = check && StringUtils.equals(subsystem.getProcessedComment(), namePartRevision.getProcessorComment()); + + // add to count + if (check) { + id_ok_namePartRevision.add(subsystem.getId()); + } else { + id_nok_namePartRevision.add(subsystem.getId()); + } + } + prepareLogReport("readMigrationNamePartRevision, check, after subsystem, id_ok_namePartRevision.size: " + id_ok_namePartRevision.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, check, after subsystem, id_nok_namePartRevision.size: " + id_nok_namePartRevision.size(), reportHtml); + + // discipline + // check entry by entry + // each attribute as expected + for (Discipline discipline : disciplines) { + check = true; + namePartRevision = mapIdNamePartRevision.get(discipline.getId()); + + check = namePartRevision != null; + + check = check && discipline.getId().equals(namePartRevision.getId()); + check = check && discipline.getVersion().equals(namePartRevision.getVersion()); + check = check && discipline.getUuid().equals(namePartRevision.getNamePart().getUuid()); + + // no parent uuid for discipline + check = check && StringUtils.equals(discipline.getName(), namePartRevision.getName()); + check = check && StringUtils.equals(discipline.getMnemonic(), namePartRevision.getMnemonic()); + check = check && StringUtils.equals(discipline.getMnemonicEquivalence(), namePartRevision.getMnemonicEqClass()); + check = check && StringUtils.equals(discipline.getDescription(), namePartRevision.getDescription()); + check = check && ((discipline.getStatus() == null && namePartRevision.getStatus() == null) + || (discipline.getStatus().name().equals(namePartRevision.getStatus().name()))); + + // latest + // true if id = get max id for uuid + // special rules for pending, not consider pending + check = check && discipline.isLatest() == discipline.getId().equals(mapUuidMaxIdDiscipline.get(discipline.getUuid())); + check = check && discipline.isDeleted() == namePartRevision.isDeleted(); + + // date may be in different format for different objects, to be formatted before being compared + check = check && ((discipline.getRequested() == null && namePartRevision.getRequestDate() == null) + || StringUtils.equals(SDF.format(discipline.getRequested()), SDF.format(namePartRevision.getRequestDate()))); + check = check && (discipline.getRequestedBy() == null && namePartRevision.getRequestedBy() == null + || StringUtils.equals(discipline.getRequestedBy(), namePartRevision.getRequestedBy().getUsername())); + check = check && StringUtils.equals(discipline.getRequestedComment(), namePartRevision.getRequesterComment()); + check = check && ((discipline.getProcessed() == null && namePartRevision.getProcessDate() == null) + || StringUtils.equals(SDF.format(discipline.getProcessed()), SDF.format(namePartRevision.getProcessDate()))); + check = check && (discipline.getProcessedBy() == null && namePartRevision.getProcessedBy() == null + || StringUtils.equals(discipline.getProcessedBy(), namePartRevision.getProcessedBy().getUsername())); + check = check && StringUtils.equals(discipline.getProcessedComment(), namePartRevision.getProcessorComment()); + + // add to count + if (check) { + id_ok_namePartRevision.add(discipline.getId()); + } else { + id_nok_namePartRevision.add(discipline.getId()); + } + } + prepareLogReport("readMigrationNamePartRevision, check, after discipline, id_ok_namePartRevision.size: " + id_ok_namePartRevision.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, check, after discipline, id_nok_namePartRevision.size: " + id_nok_namePartRevision.size(), reportHtml); + + // device group + // check entry by entry + // each attribute as expected + for (DeviceGroup deviceGroup : deviceGroups) { + check = true; + namePartRevision = mapIdNamePartRevision.get(deviceGroup.getId()); + + check = namePartRevision != null; + + check = check && deviceGroup.getId().equals(namePartRevision.getId()); + check = check && deviceGroup.getVersion().equals(namePartRevision.getVersion()); + check = check && deviceGroup.getUuid().equals(namePartRevision.getNamePart().getUuid()); + + // parent uuid + check = check && deviceGroup.getParentUuid().equals(namePartRevision.getParent().getUuid()); + check = check && StringUtils.equals(deviceGroup.getName(), namePartRevision.getName()); + check = check && StringUtils.equals(deviceGroup.getMnemonic(), namePartRevision.getMnemonic()); + check = check && StringUtils.equals(deviceGroup.getMnemonicEquivalence(), namePartRevision.getMnemonicEqClass()); + check = check && StringUtils.equals(deviceGroup.getDescription(), namePartRevision.getDescription()); + check = check && ((deviceGroup.getStatus() == null && namePartRevision.getStatus() == null) + || (deviceGroup.getStatus().name().equals(namePartRevision.getStatus().name()))); + + // latest + // true if id = get max id for uuid + // special rules for pending, not consider pending + check = check && deviceGroup.isLatest() == deviceGroup.getId().equals(mapUuidMaxIdDeviceGroup.get(deviceGroup.getUuid())); + check = check && deviceGroup.isDeleted() == namePartRevision.isDeleted(); + + // date may be in different format for different objects, to be formatted before being compared + check = check && ((deviceGroup.getRequested() == null && namePartRevision.getRequestDate() == null) + || StringUtils.equals(SDF.format(deviceGroup.getRequested()), SDF.format(namePartRevision.getRequestDate()))); + check = check && (deviceGroup.getRequestedBy() == null && namePartRevision.getRequestedBy() == null + || StringUtils.equals(deviceGroup.getRequestedBy(), namePartRevision.getRequestedBy().getUsername())); + check = check && StringUtils.equals(deviceGroup.getRequestedComment(), namePartRevision.getRequesterComment()); + check = check && ((deviceGroup.getProcessed() == null && namePartRevision.getProcessDate() == null) + || StringUtils.equals(SDF.format(deviceGroup.getProcessed()), SDF.format(namePartRevision.getProcessDate()))); + check = check && (deviceGroup.getProcessedBy() == null && namePartRevision.getProcessedBy() == null + || StringUtils.equals(deviceGroup.getProcessedBy(), namePartRevision.getProcessedBy().getUsername())); + check = check && StringUtils.equals(deviceGroup.getProcessedComment(), namePartRevision.getProcessorComment()); + + // add to count + if (check) { + id_ok_namePartRevision.add(deviceGroup.getId()); + } else { + id_nok_namePartRevision.add(deviceGroup.getId()); + } + } + prepareLogReport("readMigrationNamePartRevision, check, after devicegroup, id_ok_namePartRevision.size: " + id_ok_namePartRevision.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, check, after devicegroup, id_nok_namePartRevision.size: " + id_nok_namePartRevision.size(), reportHtml); + + // device type + // check entry by entry + // each attribute as expected + for (DeviceType deviceType : deviceTypes) { + check = true; + namePartRevision = mapIdNamePartRevision.get(deviceType.getId()); + + check = namePartRevision != null; + + check = check && deviceType.getId().equals(namePartRevision.getId()); + check = check && deviceType.getVersion().equals(namePartRevision.getVersion()); + check = check && deviceType.getUuid().equals(namePartRevision.getNamePart().getUuid()); + + // parent uuid + check = check && deviceType.getParentUuid().equals(namePartRevision.getParent().getUuid()); + check = check && StringUtils.equals(deviceType.getName(), namePartRevision.getName()); + check = check && StringUtils.equals(deviceType.getMnemonic(), namePartRevision.getMnemonic()); + check = check && StringUtils.equals(deviceType.getMnemonicEquivalence(), namePartRevision.getMnemonicEqClass()); + check = check && StringUtils.equals(deviceType.getDescription(), namePartRevision.getDescription()); + check = check && ((deviceType.getStatus() == null && namePartRevision.getStatus() == null) + || (deviceType.getStatus().name().equals(namePartRevision.getStatus().name()))); + + // latest + // true if id = get max id for uuid + // special rules for pending, not consider pending + check = check && deviceType.isLatest() == deviceType.getId().equals(mapUuidMaxIdDeviceType.get(deviceType.getUuid())); + check = check && deviceType.isDeleted() == namePartRevision.isDeleted(); + + // date may be in different format for different objects, to be formatted before being compared + check = check && ((deviceType.getRequested() == null && namePartRevision.getRequestDate() == null) + || StringUtils.equals(SDF.format(deviceType.getRequested()), SDF.format(namePartRevision.getRequestDate()))); + check = check && (deviceType.getRequestedBy() == null && namePartRevision.getRequestedBy() == null + || StringUtils.equals(deviceType.getRequestedBy(), namePartRevision.getRequestedBy().getUsername())); + check = check && StringUtils.equals(deviceType.getRequestedComment(), namePartRevision.getRequesterComment()); + check = check && ((deviceType.getProcessed() == null && namePartRevision.getProcessDate() == null) + || StringUtils.equals(SDF.format(deviceType.getProcessed()), SDF.format(namePartRevision.getProcessDate()))); + check = check && (deviceType.getProcessedBy() == null && namePartRevision.getProcessedBy() == null + || StringUtils.equals(deviceType.getProcessedBy(), namePartRevision.getProcessedBy().getUsername())); + check = check && StringUtils.equals(deviceType.getProcessedComment(), namePartRevision.getProcessorComment()); + + // add to count + if (check) { + id_ok_namePartRevision.add(deviceType.getId()); + } else { + id_nok_namePartRevision.add(deviceType.getId()); + } + } + prepareLogReport("readMigrationNamePartRevision, check, after devicetype, id_ok_namePartRevision.size: " + id_ok_namePartRevision.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, check, after devicetype, id_nok_namePartRevision.size: " + id_nok_namePartRevision.size(), reportHtml); + + // ok if + // all entries ok + // no entry nok + boolean ok = id_ok_namePartRevision.size() == namePartRevisions.size() + && id_nok_namePartRevision.size() == 0; + + prepareLogReport("readMigrationNamePartRevision, check, after, id_ok_namePartRevision.size: " + id_ok_namePartRevision.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, check, after, id_nok_namePartRevision.size: " + id_nok_namePartRevision.size(), reportHtml); + prepareLogReport("readMigrationNamePartRevision, ok: " + ok, reportHtml); + + return reportHtml.toString(); + } + + // ---------------------------------------------------------------------------------------------------- + + /** + * Perform verification of data in sense that all data can be reached. Current data is one thing but suggested data (pending) and old data (obsolete) is another thing. + * All data reached verification can be done like selecting all entries from tables (names, system group, system, subsystem, discipline, device group, device type) + * and then take uuid and retrieve history for uuid. By that, all entries should be reached. In a sense, select distinct uuid, then retrieve history by uuid, + * all ids should be encompassed by looking at all returned rows. Requires a couple of for loops and maps and sets to keep track of things. + * + * This verification concerns itself with new database, not old database. + * + * @return report of reachability of data + */ + @GetMapping ("/data_reachable") + public String readDataReachable() { + StringBuilder reportHtml = new StringBuilder(); + + List<Name> names = holderIRepositories.getNameRepository().findAll(); + List<SystemGroup> systemGroups = holderIRepositories.getSystemGroupRepository().findAll(); + List<System> systems = holderIRepositories.getSystemRepository().findAll(); + List<Subsystem> subsystems = holderIRepositories.getSubsystemRepository().findAll(); + List<Discipline> disciplines = holderIRepositories.getDisciplineRepository().findAll(); + List<DeviceGroup> deviceGroups = holderIRepositories.getDeviceGroupRepository().findAll(); + List<DeviceType> deviceTypes = holderIRepositories.getDeviceTypeRepository().findAll(); + + prepareLogReport("readDataReachable, find data, names.size: " + names.size(), reportHtml); + prepareLogReport("readDataReachable, find data, systemGroups.size: " + systemGroups.size(), reportHtml); + prepareLogReport("readDataReachable, find data, systems.size: " + systems.size(), reportHtml); + prepareLogReport("readDataReachable, find data, subsystems.size: " + subsystems.size(), reportHtml); + prepareLogReport("readDataReachable, find data, disciplines.size: " + disciplines.size(), reportHtml); + prepareLogReport("readDataReachable, find data, deviceGroups.size: " + deviceGroups.size(), reportHtml); + prepareLogReport("readDataReachable, find data, deviceTypes.size: " + deviceTypes.size(), reportHtml); + + HashSet<Long> setIdName = new HashSet<>(); + HashSet<Long> setIdSystemGroup = new HashSet<>(); + HashSet<Long> setIdSystem = new HashSet<>(); + HashSet<Long> setIdSubsystem = new HashSet<>(); + HashSet<Long> setIdDiscipline = new HashSet<>(); + HashSet<Long> setIdDeviceGroup = new HashSet<>(); + HashSet<Long> setIdDeviceType = new HashSet<>(); + + List<Name> namesByUuid = null; + List<SystemGroup> systemGroupsByUuid = null; + List<System> systemsByUuid = null; + List<Subsystem> subsystemsByUuid = null; + List<Discipline> disciplinesByUuid = null; + List<DeviceGroup> deviceGroupsByUuid = null; + List<DeviceType> deviceTypesByUuid = null; + + prepareLogReport("readDataReachable, check, before", reportHtml); + + // get started + // go through list(s) + // get entries per uuid - to mimic find history through REST API by uuid + // keep track of ids in set(s) + // ok if set(s) size same as list(s) size + // any entry not in set or once in set + + for (Name name : names) { + // to mimic - Find history for name by uuid + namesByUuid = holderIRepositories.getNameRepository().findByUuid(name.getUuid().toString()); + for (Name nameByUuid : namesByUuid) { + setIdName.add(nameByUuid.getId()); + } + } + + prepareLogReport("readDataReachable, check, after name, setIdName.size: " + setIdName.size(), reportHtml); + + for (SystemGroup systemGroup : systemGroups) { + // to mimic - Find history for system group by uuid + systemGroupsByUuid = holderIRepositories.getSystemGroupRepository().findByUuid(systemGroup.getUuid().toString()); + for (SystemGroup systemGroupByUuid : systemGroupsByUuid) { + setIdSystemGroup.add(systemGroupByUuid.getId()); + } + } + + prepareLogReport("readDataReachable, check, after systemgroup, setIdSystemGroup.size: " + setIdSystemGroup.size(), reportHtml); + + for (System system : systems) { + // to mimic - Find history for system by uuid + systemsByUuid = holderIRepositories.getSystemRepository().findByUuid(system.getUuid().toString()); + for (System systemByUuid : systemsByUuid) { + setIdSystem.add(systemByUuid.getId()); + } + } + + prepareLogReport("readDataReachable, check, after system, setIdSystem.size: " + setIdSystem.size(), reportHtml); + + for (Subsystem subsystem : subsystems) { + // to mimic - Find history for subsystem by uuid + subsystemsByUuid = holderIRepositories.getSubsystemRepository().findByUuid(subsystem.getUuid().toString()); + for (Subsystem subsystemByUuid : subsystemsByUuid) { + setIdSubsystem.add(subsystemByUuid.getId()); + } + } + + prepareLogReport("readDataReachable, check, after subsystem, setIdSubsystem.size: " + setIdSubsystem.size(), reportHtml); + + for (Discipline discipline : disciplines) { + // to mimic - Find history for discipline by uuid + disciplinesByUuid = holderIRepositories.getDisciplineRepository().findByUuid(discipline.getUuid().toString()); + for (Discipline disciplineByUuid : disciplinesByUuid) { + setIdDiscipline.add(disciplineByUuid.getId()); + } + } + + prepareLogReport("readDataReachable, check, after discipline, setIdDiscipline.size: " + setIdDiscipline.size(), reportHtml); + + for (DeviceGroup deviceGroup : deviceGroups) { + // to mimic - Find history for device group by uuid + deviceGroupsByUuid = holderIRepositories.getDeviceGroupRepository().findByUuid(deviceGroup.getUuid().toString()); + for (DeviceGroup deviceGroupByUuid : deviceGroupsByUuid) { + setIdDeviceGroup.add(deviceGroupByUuid.getId()); + } + } + + prepareLogReport("readDataReachable, check, after devicegroup, setIdDeviceGroup.size: " + setIdDeviceGroup.size(), reportHtml); + + for (DeviceType deviceType : deviceTypes) { + // to mimic - Find history for device type by uuid + deviceTypesByUuid = holderIRepositories.getDeviceTypeRepository().findByUuid(deviceType.getUuid().toString()); + for (DeviceType deviceTypeByUuid : deviceTypesByUuid) { + setIdDeviceType.add(deviceTypeByUuid.getId()); + } + } + + prepareLogReport("readDataReachable, check, after devicetype, setIdDeviceType.size: " + setIdDeviceType.size(), reportHtml); + + boolean ok = names.size() == setIdName.size() + && systemGroups.size() == setIdSystemGroup.size() + && systems.size() == setIdSystem.size() + && subsystems.size() == setIdSubsystem.size() + && disciplines.size() == setIdDiscipline.size() + && deviceGroups.size() == setIdDeviceGroup.size() + && deviceTypes.size() == setIdDeviceType.size(); + + prepareLogReport("readDataReachable, check, after", reportHtml); + prepareLogReport("readDataReachable, ok: " + ok, reportHtml); + + return reportHtml.toString(); + } + + /** + * Verify difference between old REST API and new REST API. + * + * Amount is less in new than in old. Verify that all content in new REST API is in old REST API and that difference is old/obsolete data. + * + * @return + */ + @GetMapping ("/restapi_oldvsnew") + public String readRestApiOldVsNew() { + StringBuilder reportHtml = new StringBuilder(); + + // prepare old REST API + + List<DeviceRevision> deviceRevisions = deviceRevisionRepository.findAll(); + + NameViewProvider nameViewProvider = new NameViewProvider(); + nameViewProvider.update(deviceRevisions); + + // prepare new REST API + + List<Name> names = holderIRepositories.getNameRepository().findLatestNotDeleted(); + List<Name> namesLatest = holderIRepositories.getNameRepository().findLatest(); + + prepareLogReport("readRestApiOldVsNew, find data, deviceRevisions.size: " + deviceRevisions.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, find data, nameViewProvider.getNameRevisions.entrySet.size: " + nameViewProvider.getNameRevisions().entrySet().size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, find data, names.size: " + names.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, find data, namesLatest.size: " + namesLatest.size(), reportHtml); + + // prepare comparison + + HashMap<Long, DeviceRevision> mapIdDeviceRevision = new HashMap<>((int)(nameViewProvider.getNameRevisions().entrySet().size()/0.75 + 2)); + HashMap<Long, DeviceRevision> mapIdDeviceRevisionDifference = new HashMap<>((int)(nameViewProvider.getNameRevisions().entrySet().size()/0.75 + 2)); + HashMap<Long, UUID> mapIdUuidDeviceRevision = new HashMap<>((int)(nameViewProvider.getNameRevisions().entrySet().size()/0.75 + 2)); + for (Map.Entry<String, DeviceRevision> entry : nameViewProvider.getNameRevisions().entrySet()) { + mapIdDeviceRevision.put(entry.getValue().getId(), entry.getValue()); + mapIdDeviceRevisionDifference.put(entry.getValue().getId(), entry.getValue()); + mapIdUuidDeviceRevision.put(entry.getValue().getId(), entry.getValue().getDevice().getUuid()); + } + + HashMap<Long, Name> mapIdName = new HashMap<>((int)(names.size()/0.75 + 2)); + for (Name name : names) { + mapIdName.put(name.getId(), name); + } + HashMap<UUID, Name> mapUuidNameLatest = new HashMap<>((int)(namesLatest.size()/0.75 + 2)); + for (Name name : namesLatest) { + mapUuidNameLatest.put(name.getUuid(), name); + } + + prepareLogReport("readRestApiOldVsNew, utility, mapIdDeviceRevision.size: " + mapIdDeviceRevision.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, utility, mapIdDeviceRevisionDifference.size: " + mapIdDeviceRevisionDifference.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, utility, mapIdUuidDeviceRevision.size: " + mapIdUuidDeviceRevision.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, utility, mapIdName.size: " + mapIdName.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, utility, mapUuidNameLatest.size: " + mapUuidNameLatest.size(), reportHtml); + + prepareLogReport("readRestApiOldVsNew, check, before", reportHtml); + + // compare old REST API with new REST API + // 1. difference + + boolean idInOld = true; + int countIdInOld = 0; + int countNotIdInOld = 0; + for (Name name : names) { + idInOld = mapIdDeviceRevisionDifference.containsKey(name.getId()); + if (idInOld) { + countIdInOld++; + } else if (!idInOld) { + countNotIdInOld++; + // break; + } + mapIdDeviceRevisionDifference.remove(name.getId()); + } + + prepareLogReport("readRestApiOldVsNew, check, after difference 1, mapIdDeviceRevision.size: " + mapIdDeviceRevision.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, check, after difference 1, mapIdDeviceRevisionDifference.size: " + mapIdDeviceRevisionDifference.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, check, after difference 1, mapIdUuidDeviceRevision.size: " + mapIdUuidDeviceRevision.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, check, after difference 1, mapIdName.size: " + mapIdName.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, check, after difference 1, mapUuidNameLatest.size: " + mapUuidNameLatest.size(), reportHtml); + prepareLogReport("readRestApiOldVsNew, check, after difference 1, countIdInOld: " + countIdInOld, reportHtml); + prepareLogReport("readRestApiOldVsNew, check, after difference 1, countNotIdInOld: " + countNotIdInOld, reportHtml); + + // 2. difference is old/obsolete data + // id corresponding to uuid should not be last in line + // <--> last in line should be deleted + + ArrayList<DeviceRevision> listDeviceRevisionWrong = new ArrayList<>(); + for (Entry<Long, DeviceRevision> entry : mapIdDeviceRevisionDifference.entrySet()) { + UUID uuid = mapIdUuidDeviceRevision.get(entry.getKey()); + Name name = uuid != null ? mapUuidNameLatest.get(uuid) : null; + + // something may be wrong + // if latest for devicerevision is in names then ok + // else if latest for devicerevision is deleted then ok + // else something is wrong + // if (!latestInLine && !name.isDeleted()) { + // <--> + // name != null && (name.isLatest() || name.isDeleted()) --> ok, else wrong + if (!(name != null && (name.isLatest() || name.isDeleted()))) { + // something is wrong + listDeviceRevisionWrong.add(entry.getValue()); + } + } + + prepareLogReport("readRestApiOldVsNew, check, after difference 2, listDeviceRevisionWrong.size: " + listDeviceRevisionWrong.size(), reportHtml); + + boolean ok = (mapIdDeviceRevisionDifference.size() == (nameViewProvider.getNameRevisions().entrySet().size() - names.size())) + && (listDeviceRevisionWrong.size() == 0); + + prepareLogReport("readRestApiOldVsNew, check, after", reportHtml); + prepareLogReport("readRestApiOldVsNew, ok: " + ok, reportHtml); + + return reportHtml.toString(); + } + + /** + * Utility method for preparing reports for logging purposes. + * + * @param message + * @param reportHtml + * @param reportLog + */ + private void prepareLogReport(String message, StringBuilder reportHtml) { + if (!StringUtils.isEmpty(message)) { + LOGGER.log(Level.INFO, message); + reportHtml.append(message + NEW_LINE_BR); + } + } + +} -- GitLab