216 lines
8.6 KiB
Java
216 lines
8.6 KiB
Java
package ru.resprojects.linkchecker.services;
|
|
|
|
import org.jgrapht.Graph;
|
|
import org.jgrapht.alg.interfaces.ShortestPathAlgorithm;
|
|
import org.jgrapht.alg.shortestpath.DijkstraShortestPath;
|
|
import org.jgrapht.graph.DefaultEdge;
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
import org.springframework.http.HttpStatus;
|
|
import org.springframework.stereotype.Service;
|
|
import ru.resprojects.linkchecker.AppProperties;
|
|
import ru.resprojects.linkchecker.dto.GraphDto;
|
|
import ru.resprojects.linkchecker.model.AbstractNamedEntity;
|
|
import ru.resprojects.linkchecker.model.Edge;
|
|
import ru.resprojects.linkchecker.model.Node;
|
|
import ru.resprojects.linkchecker.util.GraphUtil;
|
|
import ru.resprojects.linkchecker.util.exeptions.ApplicationException;
|
|
import ru.resprojects.linkchecker.util.exeptions.ErrorPlaceType;
|
|
import ru.resprojects.linkchecker.util.exeptions.ErrorType;
|
|
import ru.resprojects.linkchecker.util.exeptions.NotFoundException;
|
|
|
|
import java.util.ArrayList;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.Objects;
|
|
import java.util.Set;
|
|
import java.util.stream.Collectors;
|
|
|
|
import static ru.resprojects.linkchecker.dto.GraphDto.NodeGraph;
|
|
import static ru.resprojects.linkchecker.dto.GraphDto.EdgeGraph;
|
|
import static ru.resprojects.linkchecker.util.GraphUtil.*;
|
|
|
|
@Service
|
|
public class GraphServiceImpl implements GraphService {
|
|
|
|
private final GraphEdgeService edges;
|
|
private final GraphNodeService nodes;
|
|
private final AppProperties properties;
|
|
|
|
@Autowired
|
|
public GraphServiceImpl(final GraphEdgeService edges, final GraphNodeService nodes, final AppProperties properties) {
|
|
this.edges = edges;
|
|
this.nodes = nodes;
|
|
this.properties = properties;
|
|
}
|
|
|
|
@Override
|
|
public GraphDto create(final GraphDto graphTo) throws ApplicationException {
|
|
if (Objects.isNull(graphTo)) {
|
|
throw new ApplicationException(
|
|
ErrorType.DATA_ERROR,
|
|
ErrorPlaceType.GRAPH,
|
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
|
properties.getAppMsg().get("MSG_ARGUMENT_NULL")
|
|
);
|
|
}
|
|
if (graphTo.getNodes().isEmpty() && !graphTo.getEdges().isEmpty()) {
|
|
throw new ApplicationException(
|
|
ErrorType.DATA_ERROR,
|
|
ErrorPlaceType.GRAPH,
|
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
|
"NODES: " + properties.getAppMsg().get("MSG_COLLECTION_EMPTY")
|
|
);
|
|
}
|
|
clear();
|
|
GraphDto graph = graphToGraphDto(
|
|
removeCyclesFromGraph(graphBuilder(graphTo.getNodes(), graphTo.getEdges())));
|
|
Set<NodeGraph> nodeGraphSet = nodes.create(graph.getNodes());
|
|
Set<EdgeGraph> edgeGraphSet = edges.create(graph.getEdges());
|
|
return new GraphDto(nodeGraphSet, edgeGraphSet);
|
|
}
|
|
|
|
@Override
|
|
public GraphDto get() {
|
|
return removeGraphCycles(new GraphDto(nodes.getAll(), edges.getAll()));
|
|
}
|
|
|
|
@Override
|
|
public String exportToGraphViz() {
|
|
return GraphUtil.exportToGraphViz(get());
|
|
}
|
|
|
|
@Override
|
|
public void clear() {
|
|
nodes.deleteAll();
|
|
}
|
|
|
|
@Override
|
|
public String checkRoute(final Set<String> nodeNameSet) throws NotFoundException {
|
|
if (Objects.isNull(nodeNameSet)) {
|
|
throw new ApplicationException(
|
|
ErrorType.DATA_ERROR,
|
|
ErrorPlaceType.GRAPH,
|
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
|
properties.getAppMsg().get("MSG_ARGUMENT_NULL")
|
|
);
|
|
}
|
|
if (nodeNameSet.isEmpty()) {
|
|
throw new ApplicationException(
|
|
ErrorType.DATA_ERROR,
|
|
ErrorPlaceType.GRAPH,
|
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
|
properties.getAppMsg().get("MSG_COLLECTION_EMPTY")
|
|
);
|
|
}
|
|
if (nodeNameSet.size() == 1) {
|
|
throw new ApplicationException(
|
|
ErrorType.DATA_ERROR,
|
|
ErrorPlaceType.GRAPH,
|
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
|
properties.getAppMsg().get("MSG_COLLECTION_CONTAIN_ONE_ELEMENT")
|
|
);
|
|
}
|
|
GraphDto graphDto = removeGraphCycles(new GraphDto(nodes.getAll(),
|
|
edges.getAll()));
|
|
Map<String, Boolean> faultNodes = getRandomNodeFault(graphDto.getNodes());
|
|
List<String> nodeNameList = new ArrayList<>(nodeNameSet);
|
|
Graph<Node, DefaultEdge> graph = graphBuilder(graphDto.getNodes(),
|
|
graphDto.getEdges());
|
|
DijkstraShortestPath<Node, DefaultEdge> dAlg = new DijkstraShortestPath<>(graph);
|
|
Node firstNode = nodeGraphToNode(graphDto.getNodes().stream()
|
|
.filter(ng -> ng.getName().equalsIgnoreCase(nodeNameList.get(0)))
|
|
.findFirst()
|
|
.orElse(null));
|
|
if (Objects.isNull(firstNode)) {
|
|
throw new NotFoundException(
|
|
String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), nodeNameList.get(0)),
|
|
ErrorPlaceType.GRAPH
|
|
);
|
|
}
|
|
ShortestPathAlgorithm.SingleSourcePaths<Node, DefaultEdge> paths = dAlg.getPaths(firstNode);
|
|
if (faultNodes.getOrDefault(firstNode.getName(), false)) {
|
|
throw new NotFoundException(
|
|
String.format(properties.getNodeMsg().get("NODE_MSG_IS_FAULT"), firstNode.getName()),
|
|
ErrorPlaceType.GRAPH
|
|
);
|
|
}
|
|
nodeNameList.stream().skip(1).forEach(name -> {
|
|
Node nextNode = nodeGraphToNode(graphDto.getNodes().stream()
|
|
.filter(ng -> ng.getName().equalsIgnoreCase(name))
|
|
.findFirst()
|
|
.orElse(null));
|
|
if (Objects.isNull(nextNode)) {
|
|
throw new NotFoundException(
|
|
String.format(properties.getNodeMsg().get("NODE_MSG_BY_NAME_ERROR"), name),
|
|
ErrorPlaceType.GRAPH
|
|
);
|
|
}
|
|
if (faultNodes.getOrDefault(nextNode.getName(), false)) {
|
|
throw new NotFoundException(
|
|
String.format(properties.getNodeMsg().get("NODE_MSG_IS_FAULT"), name),
|
|
ErrorPlaceType.GRAPH
|
|
);
|
|
}
|
|
List<Node> findNodes = paths.getPath(nextNode).getVertexList();
|
|
List<String> findNodesName = findNodes.stream()
|
|
.map(AbstractNamedEntity::getName)
|
|
.collect(Collectors.toList());
|
|
if (!nodeNameList.containsAll(findNodesName)) {
|
|
throw new NotFoundException(
|
|
String.format(properties.getNodeMsg().get("NODE_MSG_NOT_REACHABLE"), nodeNameList.get(0), name),
|
|
ErrorPlaceType.GRAPH
|
|
);
|
|
}
|
|
});
|
|
nodeNameList.forEach(name ->
|
|
graphDto.getNodes().stream()
|
|
.filter(ng -> ng.getName().equalsIgnoreCase(name))
|
|
.findFirst()
|
|
.ifPresent(ng -> {
|
|
ng.setCounter(ng.getCounter() + 1);
|
|
nodes.update(ng);
|
|
}));
|
|
return String.format("Route for nodes %s is found", nodeNameList.toString());
|
|
}
|
|
|
|
private GraphDto removeGraphCycles(final GraphDto graph) {
|
|
if (Objects.isNull(graph) || graph.getEdges().isEmpty()) {
|
|
return graph;
|
|
}
|
|
GraphDto optimizedGraph = graphToGraphDto(
|
|
removeCyclesFromGraph(graphBuilder(graph.getNodes(), graph.getEdges())));
|
|
//Checking, was removed edges or not from graph after optimizing
|
|
if (graph.getEdges().size() == optimizedGraph.getEdges().size()) {
|
|
return graph;
|
|
}
|
|
//Because ID's lost's in optimized graph, we need recover them.
|
|
Set<EdgeGraph> optimizedEdges = graph.getEdges().stream()
|
|
.filter(e -> optimizedGraph.getEdges().stream()
|
|
.anyMatch(eg -> eg.getNodeOne().equalsIgnoreCase(e.getNodeOne())
|
|
&& eg.getNodeTwo().equalsIgnoreCase(e.getNodeTwo())))
|
|
.collect(Collectors.toSet());
|
|
//Rewrite edge collection in optimized graph.
|
|
optimizedGraph.setEdges(optimizedEdges);
|
|
//Search all edges that was removed from graph and remove them from DB.
|
|
Set<EdgeGraph> removedEdgesGraph = graph.getEdges().stream()
|
|
.filter(eg -> !optimizedGraph.getEdges().contains(eg))
|
|
.collect(Collectors.toSet());
|
|
Set<Edge> removedEdges = getEdgesFromGraphDto(new GraphDto(
|
|
optimizedGraph.getNodes(),
|
|
removedEdgesGraph));
|
|
if (!removedEdges.isEmpty()) {
|
|
edges.delete(removedEdges);
|
|
return optimizedGraph;
|
|
}
|
|
return graph;
|
|
}
|
|
|
|
public GraphEdgeService getEdges() {
|
|
return edges;
|
|
}
|
|
|
|
public GraphNodeService getNodes() {
|
|
return nodes;
|
|
}
|
|
}
|