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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .java-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
17
21
2 changes: 2 additions & 0 deletions src/main/java/com/epam/healenium/constants/FieldName.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,7 @@ public class FieldName {
public static final String CLASSES = "classes";
public static final String CLASS = "class";
public static final String OTHER = "other";
/** Parent node in the ancestor chain (serialized without children to avoid cycles). */
public static final String PARENT = "parent";

}
58 changes: 51 additions & 7 deletions src/main/java/com/epam/healenium/converter/NodeDeserializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,16 @@
import tools.jackson.databind.JsonNode;
import tools.jackson.databind.deser.std.StdDeserializer;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Deserializes a {@link Node} and restores the ancestor chain from nested {@code parent} objects.
* Returns the leaf node with {@code parent} links set (via {@link NodeBuilder#addChild}).
*/
@SuppressWarnings("unchecked")
public class NodeDeserializer extends StdDeserializer<Node> {

Expand All @@ -21,21 +29,57 @@ public NodeDeserializer() {
@Override
public Node deserialize(JsonParser parser, DeserializationContext ctxt) throws JacksonException {
JsonNode tree = ctxt.readTree(parser);
return deserializeLeafWithAncestors(tree, ctxt);
}

private Node deserializeLeafWithAncestors(JsonNode leafTree, DeserializationContext ctxt) throws JacksonException {
List<JsonNode> rootToLeaf = new ArrayList<>();
for (JsonNode current = leafTree; current != null && !current.isNull() && !current.isMissingNode();
current = current.get(FieldName.PARENT)) {
rootToLeaf.add(current);
}
Collections.reverse(rootToLeaf);

// Build root → leaf so NodeBuilder#build sets parent links on children.
Node child = null;
for (int i = rootToLeaf.size() - 1; i >= 0; i--) {
NodeBuilder builder = toBuilder(rootToLeaf.get(i), ctxt);
if (child != null) {
builder.addChild(child);
}
child = builder.build();
}

Node leaf = child;
while (leaf != null && leaf.getChildren() != null && !leaf.getChildren().isEmpty()) {
leaf = leaf.getChildren().getFirst();
}
return leaf;
}

private NodeBuilder toBuilder(JsonNode tree, DeserializationContext ctxt) throws JacksonException {
String tag = ctxt.readTreeAsValue(tree.path(FieldName.TAG), String.class);
Integer index = ctxt.readTreeAsValue(tree.path(FieldName.INDEX), Integer.class);
String innerText = ctxt.readTreeAsValue(tree.path(FieldName.INNER_TEXT), String.class);
String id = ctxt.readTreeAsValue(tree.path(FieldName.ID), String.class);
String classes = ctxt.readTreeAsValue(tree.path(FieldName.CLASSES), String.class);
Map<String, String> attributes = ctxt.readTreeAsValue(tree.path(FieldName.OTHER), Map.class);
attributes.put(FieldName.ID, id);
attributes.put(FieldName.CLASS, classes);
if (attributes == null) {
attributes = new HashMap<>();
} else {
attributes = new HashMap<>(attributes);
}
attributes.put(FieldName.ID, id != null ? id : "");
attributes.put(FieldName.CLASS, classes != null ? classes : "");

return new NodeBuilder()
NodeBuilder builder = new NodeBuilder()
.setTag(tag)
.setIndex(index)
.addContent(innerText)
.setAttributes(attributes)
.build();
.setIndex(index != null ? index : 0)
.setAttributes(attributes);
if (innerText != null) {
builder.addContent(innerText);
}
return builder;
}

}
21 changes: 21 additions & 0 deletions src/main/java/com/epam/healenium/converter/NodeSerializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,14 @@
import tools.jackson.databind.jsontype.TypeSerializer;
import tools.jackson.databind.ser.std.StdSerializer;

import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Set;

/**
* Serializes a {@link Node} including its ancestor chain via nested {@code parent} objects.
* Children are intentionally omitted to avoid cycles and oversized payloads.
*/
public class NodeSerializer extends StdSerializer<Node> {

public NodeSerializer() {
Expand All @@ -27,6 +35,12 @@ public void serializeWithType(Node value, JsonGenerator gen, SerializationContex

@Override
public void serialize(Node value, JsonGenerator gen, SerializationContext serializers) throws JacksonException {
Set<Node> visited = Collections.newSetFromMap(new IdentityHashMap<>());
visited.add(value);
serializeNode(value, gen, visited);
}

private void serializeNode(Node value, JsonGenerator gen, Set<Node> visited) throws JacksonException {
gen.writeStartObject();
gen.writeStringProperty(FieldName.TAG, value.getTag());
Integer index = value.getIndex();
Expand All @@ -39,6 +53,13 @@ public void serialize(Node value, JsonGenerator gen, SerializationContext serial
gen.writeStringProperty(FieldName.ID, value.getId());
gen.writeStringProperty(FieldName.CLASSES, String.join(" ", value.getClasses()));
gen.writePOJOProperty(FieldName.OTHER, value.getOtherAttributes());

Node parent = value.getParent();
if (parent != null && visited.add(parent)) {
gen.writeName(FieldName.PARENT);
serializeNode(parent, gen, visited);
}

gen.writeEndObject();
gen.flush();
}
Expand Down
6 changes: 6 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,12 @@ logging:
max-history: 10
total-size-cap: 100MB

# Healenium healing settings (env aliases used by docker-compose)
healenium:
selector-type: ${SELECTOR_TYPE:cssSelector}
score-cap: ${SCORE_CAP:.6}
recovery-tries: ${RECOVERY_TRIES:1}

# Proxy service configuration
proxy:
service:
Expand Down
Loading