Skip to content

Commit

Permalink
Merge branch 'hotfix-1.8.x' into hotfix-1.9.x
Browse files Browse the repository at this point in the history
  • Loading branch information
npomaroli committed Mar 22, 2023
2 parents fc2f7ad + c201afa commit c9d7960
Show file tree
Hide file tree
Showing 4 changed files with 200 additions and 3 deletions.
10 changes: 10 additions & 0 deletions LTS-CHANGELOG.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ include::content/docs/variables.adoc-include[]
The LTS changelog lists releases which are only accessible via a commercial subscription.
All fixes and changes in LTS releases will be released the next minor release. Changes from LTS 1.4.x will be included in release 1.5.0.

[[v1.9.13]]
== 1.9.13 (22.03.2023)

icon:check[] Core: If a node has more that one INITIAL content for a language in a branch, this can now be repaired with the consistency repair tool.

[[v1.9.12]]
== 1.9.12 (08.03.2023)

Expand All @@ -41,6 +46,11 @@ icon:check[] Core: The node migration process has been improved to reduce resour

icon:check[] Core: Corner case of updating the webroot info might throw a false conflict exception, when the segment field value is reset for a schema. This has been fixed.

[[v1.8.20]]
== 1.8.20 (22.03.2023)

icon:check[] Core: If a node has more that one INITIAL content for a language in a branch, this can now be repaired with the consistency repair tool.

[[v1.8.19]]
== 1.8.19 (08.03.2023)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
| Database revision


| *1.10.2*
| *1.10.3*
| 6d5ccff3

| *1.9.12*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@
import static com.gentics.mesh.core.rest.common.ContainerType.INITIAL;
import static com.gentics.mesh.core.rest.common.ContainerType.PUBLISHED;

import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

import com.gentics.mesh.cli.BootstrapInitializer;
import com.gentics.mesh.context.BulkActionContext;
import com.gentics.mesh.core.data.GraphFieldContainerEdge;
import com.gentics.mesh.core.data.HibNodeFieldContainer;
import com.gentics.mesh.core.data.HibNodeFieldContainerEdge;
import com.gentics.mesh.core.data.NodeGraphFieldContainer;
import com.gentics.mesh.core.data.container.impl.NodeGraphFieldContainerImpl;
import com.gentics.mesh.core.data.dao.PersistingContentDao;
Expand Down Expand Up @@ -125,6 +127,9 @@ private void checkGraphFieldContainer(Database db, NodeGraphFieldContainer conta
}
}

// initial GFC must not have another initial as a previous GFC for the same branch
checkInitialUniqueness(container, previous, result, attemptRepair);

// GFC must either have a next GFC, or must be the draft GFC for a Node
if (!contentDao.hasNextVersion(container) && !contentDao.isDraft(container)) {
String nodeInfo = "unknown";
Expand Down Expand Up @@ -307,4 +312,53 @@ private HibNodeFieldContainer findDraft(HibNodeFieldContainer latest) {
return null;
}

/**
* Check whether the container is the only INITIAL for its node (for all branches)
* @param container GFC to check
* @param previous previous GFC (may be null)
* @param result check result
* @param attemptRepair true to attempt repair
*/
private void checkInitialUniqueness(NodeGraphFieldContainer container, HibNodeFieldContainer previous, ConsistencyCheckResult result, boolean attemptRepair) {
PersistingContentDao contentDao = CommonTx.get().contentDao();
String uuid = container.getUuid();

if (contentDao.isInitial(container) && previous != null) {
Set<String> branchUuids = contentDao.getBranches(container, INITIAL);

String nodeInfo = "unknown";
try {
HibNode node = contentDao.getNode(container);
nodeInfo = node.getUuid();
} catch (Exception e) {
log.debug("Could not load node uuid", e);
}

Set<String> branchesWithConflict = new HashSet<>();
while (previous != null) {
for (String branchUuid : branchUuids) {
// do not check for branches, where we already found a conflicting edge
if (branchesWithConflict.contains(branchUuid)) {
continue;
}
if (contentDao.isInitial(previous, branchUuid)) {
branchesWithConflict.add(branchUuid);
boolean repaired = false;
if (attemptRepair) {
// remove the INITIAL edge
Iterator<? extends HibNodeFieldContainerEdge> edgeIterator = contentDao.getContainerEdges(container, INITIAL, branchUuid);
if (edgeIterator.hasNext()) {
contentDao.removeEdge(edgeIterator.next());
repaired = true;
}
}
result.addInconsistency(String.format(
"GraphFieldContainer of Node {%s} is INITIAL for branch %s and has another INITIAL GFC for the branch as a previous version",
nodeInfo, branchUuid), uuid, MEDIUM, repaired, DELETE);
}
}
previous = previous.getPreviousVersion();
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,48 @@
import static com.gentics.mesh.core.rest.MeshEvent.REPAIR_START;
import static com.gentics.mesh.core.rest.admin.consistency.ConsistencyRating.CONSISTENT;
import static com.gentics.mesh.core.rest.admin.consistency.ConsistencyRating.INCONSISTENT;
import static com.gentics.mesh.core.rest.job.JobStatus.COMPLETED;
import static com.gentics.mesh.test.ClientHelper.call;
import static com.gentics.mesh.test.TestSize.FULL;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import com.gentics.mesh.core.data.node.Node;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;

import org.junit.Test;

import com.gentics.mesh.core.data.GraphFieldContainerEdge;
import com.gentics.mesh.core.data.HibNodeFieldContainer;
import com.gentics.mesh.core.data.User;
import com.gentics.mesh.core.data.container.impl.NodeGraphFieldContainerImpl;
import com.gentics.mesh.core.data.dao.ContentDao;
import com.gentics.mesh.core.data.dao.NodeDao;
import com.gentics.mesh.core.data.impl.GraphFieldContainerEdgeImpl;
import com.gentics.mesh.core.data.impl.UserImpl;
import com.gentics.mesh.core.data.node.HibNode;
import com.gentics.mesh.core.data.node.Node;
import com.gentics.mesh.core.data.node.impl.NodeImpl;
import com.gentics.mesh.core.data.relationship.GraphRelationships;
import com.gentics.mesh.core.db.GraphDBTx;
import com.gentics.mesh.core.rest.admin.consistency.ConsistencyCheckResponse;
import com.gentics.mesh.core.rest.admin.consistency.ConsistencyRating;
import com.gentics.mesh.core.rest.admin.consistency.InconsistencyInfo;
import com.gentics.mesh.core.rest.admin.consistency.InconsistencySeverity;
import com.gentics.mesh.core.rest.admin.consistency.RepairAction;
import com.gentics.mesh.core.rest.branch.BranchCreateRequest;
import com.gentics.mesh.core.rest.common.ContainerType;
import com.gentics.mesh.core.rest.node.FieldMap;
import com.gentics.mesh.core.rest.node.NodeResponse;
import com.gentics.mesh.core.rest.node.NodeUpdateRequest;
import com.gentics.mesh.core.rest.node.field.impl.StringFieldImpl;
import com.gentics.mesh.parameter.client.NodeParametersImpl;
import com.gentics.mesh.parameter.client.VersioningParametersImpl;
import com.gentics.mesh.test.MeshTestSetting;
import com.gentics.mesh.test.context.AbstractMeshTest;
import com.syncleus.ferma.TEdge;

@MeshTestSetting(testSize = FULL, startServer = true, inMemoryDB = true)
public class ConsistencyCheckTest extends AbstractMeshTest {
Expand Down Expand Up @@ -85,4 +109,113 @@ public void testConsistencyRepair() {

}

/**
* Test repairing multiple INITIAL edges
*/
@Test
public void testRepairMultipleInitial() {
grantAdmin();

String branchName = "newbranch";
String nodeUuid = tx(() -> content().getUuid());
String projectName = tx(() -> project().getName());

// modify the content in the initial branch (in all languages)
NodeResponse nodeResponse = call(() -> client().findNodeByUuid(projectName, nodeUuid));
Set<String> languages = nodeResponse.getAvailableLanguages().keySet();

for (String language : languages) {
NodeResponse draft = call(() -> client().findNodeByUuid(projectName, nodeUuid,
new VersioningParametersImpl().setVersion("draft"),
new NodeParametersImpl().setLanguages(language)));
FieldMap fields = draft.getFields();
StringFieldImpl titleField = fields.getStringField("title");
titleField.setString(titleField.getString() + " modified");
fields.put("title", titleField);
call(() -> client().updateNode(projectName, nodeUuid,
new NodeUpdateRequest().setLanguage(language).setFields(fields)));
}

AtomicReference<String> newBranchUuid = new AtomicReference<>();
// create new latest branch
waitForJobs(() -> {
newBranchUuid.set(call(() -> client().createBranch(projectName, new BranchCreateRequest().setName(branchName).setLatest(true))).getUuid());
}, COMPLETED, 1);

// modify the content also in the new branch
for (String language : languages) {
NodeResponse draft = call(() -> client().findNodeByUuid(projectName, nodeUuid,
new VersioningParametersImpl().setVersion("draft"),
new NodeParametersImpl().setLanguages(language)));
FieldMap fields = draft.getFields();
StringFieldImpl titleField = fields.getStringField("title");
titleField.setString(titleField.getString() + " modified");
fields.put("title", titleField);
call(() -> client().updateNode(projectName, nodeUuid,
new NodeUpdateRequest().setLanguage(language).setFields(fields)));
}

// check consistency
ConsistencyCheckResponse response = call(() -> client().checkConsistency());
assertThat(response.getInconsistencies()).isEmpty();
assertThat(response.getResult()).as("Result").isEqualTo(ConsistencyRating.CONSISTENT);

// make an inconsistency by adding another INITIAL edge
AtomicReference<String> contentUuid = new AtomicReference<>();
tx(tx -> {
NodeDao nodeDao = tx.nodeDao();
ContentDao contentDao = tx.contentDao();
HibNode node = nodeDao.findByUuidGlobal(nodeUuid);
HibNodeFieldContainer enDraft = contentDao.getFieldContainer(node, "en", newBranchUuid.get(), ContainerType.DRAFT);
contentUuid.set(enDraft.getUuid());

GraphFieldContainerEdge edge = ((NodeImpl) node).addFramedEdge(GraphRelationships.HAS_FIELD_CONTAINER,
(NodeGraphFieldContainerImpl) enDraft, GraphFieldContainerEdgeImpl.class);
edge.setBranchUuid(newBranchUuid.get());
edge.setLanguageTag("en");
edge.setType(ContainerType.INITIAL);
});

// must be inconsistent now
response = call(() -> client().checkConsistency());
assertThat(response.getInconsistencies()).usingFieldByFieldElementComparator().containsOnly(
new InconsistencyInfo()
.setDescription(String.format("The node has more than one GFC of type %s, language %s for branch %s", ContainerType.INITIAL, "en", newBranchUuid.get()))
.setElementUuid(nodeUuid)
.setRepairAction(RepairAction.NONE)
.setRepaired(false)
.setSeverity(InconsistencySeverity.HIGH),
new InconsistencyInfo()
.setDescription(String.format("GraphFieldContainer of Node {%s} is INITIAL for branch %s and has another INITIAL GFC for the branch as a previous version", nodeUuid, newBranchUuid.get()))
.setElementUuid(contentUuid.get())
.setRepairAction(RepairAction.DELETE)
.setRepaired(false)
.setSeverity(InconsistencySeverity.MEDIUM)
);
assertThat(response.getResult()).as("Result").isEqualTo(ConsistencyRating.INCONSISTENT);

// repair
response = call(() -> client().repairConsistency());
assertThat(response.getInconsistencies()).usingFieldByFieldElementComparator().containsOnly(
new InconsistencyInfo()
.setDescription(String.format("The node has more than one GFC of type %s, language %s for branch %s", ContainerType.INITIAL, "en", newBranchUuid.get()))
.setElementUuid(nodeUuid)
.setRepairAction(RepairAction.NONE)
.setRepaired(false)
.setSeverity(InconsistencySeverity.HIGH),
new InconsistencyInfo()
.setDescription(String.format("GraphFieldContainer of Node {%s} is INITIAL for branch %s and has another INITIAL GFC for the branch as a previous version", nodeUuid, newBranchUuid.get()))
.setElementUuid(contentUuid.get())
.setRepairAction(RepairAction.DELETE)
.setRepaired(true)
.setSeverity(InconsistencySeverity.MEDIUM)
);
// since we found the consistency twice (and had no clue how to repair the first one), we are still "inconsistent"
assertThat(response.getResult()).as("Result").isEqualTo(ConsistencyRating.INCONSISTENT);

// check again
response = call(() -> client().checkConsistency());
assertThat(response.getInconsistencies()).isEmpty();
assertThat(response.getResult()).as("Result").isEqualTo(ConsistencyRating.CONSISTENT);
}
}

0 comments on commit c9d7960

Please sign in to comment.