/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package com.aliyun.odps.graph;
import java.io.IOException;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import com.aliyun.odps.io.Writable;
import com.aliyun.odps.io.WritableComparable;
/**
* DefaultLoadingVertexResolver 用于解决 {@link GraphLoader} 载入图数据时引入的图拓扑修改及冲突.
*
* <p>
* 在图载入阶段,用户可以调用 {@link MutationContext} 的接口向图中添加、删除点或边,由此引入的冲突默认由此类解决, 用户也可以通过
* {@link GraphJob} 提供的
* {@linkplain JobConf#setLoadingVertexResolverClass(Class)
* setLoadingVertexResolverClass} 方法指定自己的实现。
* </p>
*
* <p>
* 对于同一个点ID,DefaultLoadingVertexResolver 解决该点在图载入阶段的冲突是按照以下顺序进行的:
* <ol>
* <li>解决 {@linkplain MutationContext#addVertexRequest(Vertex) addVertexRequest}
* 引起的冲突: 添加点时,如果点已经存在或者待添加的点存在重复边(起始和终点相同的边),则报错。
* <li>解决 {@linkplain MutationContext#addEdgeRequest(WritableComparable, Edge)
* addEdgeRequest} 引起的冲突: 添加边时,如果对应起始点不存在或者导致重复边,则报错。
* <li>
* 解决
* {@linkplain MutationContext#removeEdgeRequest(WritableComparable, WritableComparable)
* removeEdgeRequest} 引起的冲突: 删除边时,如果该边不存在,则报错。
* <li>解决 {@linkplain MutationContext#removeVertexRequest(WritableComparable)
* removeVertexRequest} 引起的冲突:删除点时,如果该点不存在,则报错。
* </ol>
* </p>
*
* @param <I>
* Vertex ID 类型
* @param <V>
* Vertex Value 类型
* @param <E>
* Edge Value 类型
* @param <M>
* Message 类型
* @see JobConf#setLoadingVertexResolverClass(Class)
*/
@SuppressWarnings("rawtypes")
public class DefaultLoadingVertexResolver<I extends WritableComparable, V extends Writable, E extends Writable, M extends Writable>
extends VertexResolver<I, V, E, M> {
/**
* 提供图载入时的默认冲突处理方法.
*
*
* <p>
* 首先处理添加点和边的请求,再处理删除边和点的请求,详细处理规则见:{@linkplain DefaultLoadingVertexResolver
* 本类说明}
* </p>
*
* @param vertexId
* 冲突点的ID
* @param vertex
* 已存在的{@link Vertex}对象,在图载入阶段,始终为 null
* @param vertexChanges
* 关于该点的添加和删除请求
* @param hasMessages
* 该点是否有输入消息,在图载入阶段,始终为false
*/
@Override
public Vertex<I, V, E, M> resolve(I vertexId, Vertex<I, V, E, M> vertex,
VertexChanges<I, V, E, M> vertexChanges, boolean hasMessages)
throws IOException {
/**
* 1. If creation of vertex desired, pick first vertex. 2. If got added
* edges, create vertex.
*/
vertex = addVertexIfDesired(vertexId, vertexChanges);
/** 3. If edge addition, add the edges */
addEdges(vertexId, vertex, vertexChanges);
/** 4. If the vertex exists, first prune the edges. */
removeEdges(vertexId, vertex, vertexChanges);
/** 5. If vertex removal desired, remove the vertex. */
vertex = removeVertexIfDesired(vertexId, vertex, vertexChanges);
return vertex;
}
/**
* 图载入阶段,处理删除边的请求。
*
* @param vertexId
* 请求删除的边所在的点的ID
* @param vertex
* 请求删除的边所在的点
* @param vertexChanges
* 包含要从该点中删除的边
* @throws IOException
* 请求删除边时,点不存在,或者删除不存在的边
*/
protected void removeEdges(I vertexId, Vertex<I, V, E, M> vertex,
VertexChanges<I, V, E, M> vertexChanges) throws IOException {
if (hasEdgeRemovals(vertexChanges)) {
if (vertex == null) {
throw new IOException("ODPS-0730001: GraphLoader remove edge from vertex(Id: '"
+ vertexId + "') that does not exist");
}
if (!vertex.hasEdges()) {
throw new IOException("ODPS-0730001: GraphLoader remove edge from vertex(Id: '"
+ vertexId + "') that does not has edges");
}
for (I removedDestVertex : vertexChanges.getRemovedEdgeList()) {
boolean found = false;
List<Edge<I, E>> edgeList = vertex.getEdges();
for (Iterator<Edge<I, E>> edges = edgeList.iterator(); edges.hasNext(); ) {
Edge<I, E> edge = edges.next();
if (edge.getDestVertexId().equals(removedDestVertex)) {
edges.remove();
found = true;
}
}
if (!found) {
throw new IOException("ODPS-0730001: GraphLoader remove edge(DestVertexId: '"
+ removedDestVertex + "') that does not exist for " + vertex);
}
}
}
}
/**
* 图载入阶段,处理删除点的请求。如果存在删除点的请求,则返回null,否则返回输入 的{@link Vertex}参数.
*
* @param vertexId
* 请求删除的点的ID
* @param vertex
* 请求删除的点
* @param vertexChanges
* 包含是否要删除点的请求
* @return 如果请求删除点,则返回null,否则返回vertex本身
* @throws IOException
* 请求删除点时,点不存在
*/
protected Vertex<I, V, E, M> removeVertexIfDesired(I vertexId,
Vertex<I, V, E, M> vertex,
VertexChanges<I, V, E, M> vertexChanges)
throws IOException {
if (hasVertexRemovals(vertexChanges)) {
if (vertex == null) {
throw new IOException("ODPS-0730001: GraphLoader remove vertex(Id: '" + vertexId
+ "') that does not exist");
} else {
vertex = null;
}
}
return vertex;
}
/**
* 图载入阶段,处理添加点的请求.
*
* @param vertexId
* 请求添加的点的ID
* @param vertexChanges
* 包含请求添加的点
* @return 请求添加的点,或者没有请求添加点时,返回null
* @throws IOException
* 多次(大于一次)请求添加点
*/
protected Vertex<I, V, E, M> addVertexIfDesired(I vertexId,
VertexChanges<I, V, E, M> vertexChanges)
throws IOException {
Vertex<I, V, E, M> vertex = null;
if (hasVertexAdditions(vertexChanges)) {
if (vertexChanges.getAddedVertexList().size() > 1) {
throw new IOException("ODPS-0730001: GraphLoader load duplicate vertices for id: '"
+ vertexId + "'");
}
vertex = vertexChanges.getAddedVertexList().get(0);
}
return vertex;
}
/**
* 图载入阶段,处理添加边的请求.
*
* @param vertexId
* 请求添加的边所在的点的ID
* @param vertex
* 请求点的边所在在的点
* @param vertexChanges
* 包含请求添加的边
* @throws IOException
* 点本身拥有的边存在重复边,或者请求添加的边中存在重复边
*/
protected void addEdges(I vertexId, Vertex<I, V, E, M> vertex,
VertexChanges<I, V, E, M> vertexChanges) throws IOException {
Set<I> destVertexId = new HashSet<I>();
if (vertex != null && vertex.hasEdges()) {
for (Edge<I, E> edge : vertex.getEdges()) {
if (destVertexId.contains(edge.getDestVertexId())) {
throw new IOException(
"ODPS-0730001: GraphLoader load duplicate edges for vertex, from '"
+ vertex.getId() + "' to '" + edge.getDestVertexId() + "'");
}
destVertexId.add(edge.getDestVertexId());
}
}
if (hasEdgeAdditions(vertexChanges)) {
if (vertex == null) {
throw new IOException("ODPS-0730001: GraphLoader add edge to vertex(Id: '" + vertexId
+ "') that does not exist");
}
/** Check whether or not vertex has duplicate edges */
for (Edge<I, E> edge : vertexChanges.getAddedEdgeList()) {
if (destVertexId.contains(edge.getDestVertexId())) {
throw new IOException(
"ODPS-0730001: GraphLoader load duplicate edges for vertex, from '"
+ vertex.getId() + "' to '" + edge.getDestVertexId() + "'");
}
destVertexId.add(edge.getDestVertexId());
vertex.addEdge(edge.getDestVertexId(), edge.getValue());
}
}
}
/**
* 检查是否存在删除点的请求。
*
* @param changes
* 待检查的点变化的集合
* @return 集合中包含删除点的请求,返回true,否则返回false
*/
protected boolean hasVertexRemovals(VertexChanges<I, V, E, M> changes) {
return changes != null && changes.getRemovedVertexCount() > 0;
}
/**
* 检查是否存在添加点的请求。
*
* @param changes
* 待检查的点变化的集合
* @return 集合中包含添加点的请求,返回true,否则返回false
*/
protected boolean hasVertexAdditions(VertexChanges<I, V, E, M> changes) {
return changes != null && changes.getAddedVertexList() != null
&& !changes.getAddedVertexList().isEmpty();
}
/**
* 检查是否存在添加边的请求。
*
* @param changes
* 待检查的点变化的集合
* @return 集合中包含添加边的请求,则返回true,否则返回false
*/
protected boolean hasEdgeAdditions(VertexChanges<I, V, E, M> changes) {
return changes != null && changes.getAddedEdgeList() != null
&& !changes.getAddedEdgeList().isEmpty();
}
/**
* 检查是否存在删除边的请求。
*
* @param changes
* 待检查的点变化的集合
* @return 集合中包含删除边的请求,则返回true,否则返回false
*/
protected boolean hasEdgeRemovals(VertexChanges<I, V, E, M> changes) {
return changes != null && changes.getRemovedEdgeList() != null
&& !changes.getRemovedEdgeList().isEmpty();
}
}