blob: 74ba99f9da9edd68f4dc0a553216fe664dc90f9c [file] [log] [blame]
/*
* Copyright 2017 HugeGraph Authors
*
* 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.baidu.hugegraph.serializer.direct.struct;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.CharsetEncoder;
import java.nio.charset.CoderResult;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.ListUtils;
import org.apache.commons.lang3.StringUtils;
import com.baidu.hugegraph.driver.HugeClient;
import com.baidu.hugegraph.serializer.direct.util.DataTypeUtil;
import com.baidu.hugegraph.serializer.direct.util.GraphSchema;
import com.baidu.hugegraph.structure.GraphElement;
import com.baidu.hugegraph.structure.constant.IdStrategy;
import com.baidu.hugegraph.structure.graph.Vertex;
import com.baidu.hugegraph.structure.schema.EdgeLabel;
import com.baidu.hugegraph.structure.schema.PropertyKey;
import com.baidu.hugegraph.structure.schema.SchemaLabel;
import com.baidu.hugegraph.structure.schema.VertexLabel;
import com.baidu.hugegraph.util.E;
import com.baidu.hugegraph.util.LongEncoding;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableList;
public abstract class ElementBuilder<GE extends GraphElement> {
private final GraphSchema schema;
// NOTE: CharsetEncoder is not thread safe
private final CharsetEncoder encoder;
private final ByteBuffer buffer;
public ElementBuilder(HugeClient client) {
this.schema = new GraphSchema(client);
this.encoder = Charsets.UTF_8.newEncoder();
this.buffer = ByteBuffer.allocate(128);
}
public abstract List<GE> build(String[] names, Object[] values);
public abstract SchemaLabel schemaLabel();
protected abstract Collection<String> nonNullableKeys();
protected abstract boolean isIdField(String fieldName);
@SuppressWarnings("unchecked")
protected Collection<String> nonNullableKeys(SchemaLabel schemaLabel) {
return CollectionUtils.subtract(schemaLabel.properties(),
schemaLabel.nullableKeys());
}
protected VertexKVPairs newKVPairs(VertexLabel vertexLabel,
boolean unfold) {
IdStrategy idStrategy = vertexLabel.idStrategy();
if (idStrategy.isCustomize()) {
if (unfold) {
return new VertexFlatIdKVPairs(vertexLabel);
} else {
return new VertexIdKVPairs(vertexLabel);
}
} else {
assert idStrategy.isPrimaryKey();
if (unfold) {
return new VertexFlatPkKVPairs(vertexLabel);
} else {
return new VertexPkKVPairs(vertexLabel);
}
}
}
protected void addProperty(GraphElement element, String key, Object value) {
this.addProperty(element, key, value, true);
}
protected void addProperty(GraphElement element, String key, Object value,
boolean needConvert) {
if (needConvert) {
value = this.convertPropertyValue(key, value);
}
element.property(key, value);
}
protected void addProperties(GraphElement element,
Map<String, Object> properties) {
for (Map.Entry<String, Object> entry : properties.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
this.checkFieldValue(key, value);
value = this.convertPropertyValue(key, value);
element.property(key, value);
}
}
protected void checkNonNullableKeys(GraphElement element) {
Set<String> keys = element.properties().keySet();
// Check whether passed all non-null property
Collection<String> requiredKeys = this.nonNullableKeys();
if (!keys.containsAll(requiredKeys)) {
@SuppressWarnings("unchecked")
Collection<String> missed = CollectionUtils.subtract(requiredKeys,
keys);
E.checkArgument(false, "All non-null property keys %s of '%s' " +
"must be setted, but missed keys %s",
requiredKeys, this.schemaLabel().name(), missed);
}
}
protected PropertyKey getPropertyKey(String name) {
return this.schema.getPropertyKey(name);
}
protected VertexLabel getVertexLabel(String name) {
return this.schema.getVertexLabel(name);
}
protected EdgeLabel getEdgeLabel(String name) {
return this.schema.getEdgeLabel(name);
}
protected Object mappingValue(String fieldName, Object fieldValue) {
if (this.mapping().mappingValues().isEmpty()) {
return fieldValue;
}
String fieldStrValue = String.valueOf(fieldValue);
return this.mapping().mappingValue(fieldName, fieldStrValue);
}
private void customizeId(VertexLabel vertexLabel, Vertex vertex,
String idField, Object idValue) {
E.checkArgumentNotNull(idField, "The vertex id field can't be null");
E.checkArgumentNotNull(idValue, "The vertex id value can't be null");
IdStrategy idStrategy = vertexLabel.idStrategy();
if (idStrategy.isCustomizeString()) {
String id = (String) idValue;
this.checkVertexIdLength(id);
vertex.id(id);
} else if (idStrategy.isCustomizeNumber()) {
Long id = DataTypeUtil.parseNumber(idField, idValue);
vertex.id(id);
} else {
assert idStrategy.isCustomizeUuid();
UUID id = DataTypeUtil.parseUUID(idField, idValue);
vertex.id(id);
}
}
private Object convertPropertyValue(String key, Object rawValue) {
PropertyKey propertyKey = this.getPropertyKey(key);
InputSource inputSource = this.struct.input();
return DataTypeUtil.convert(rawValue, propertyKey, inputSource);
}
private void checkFieldValue(String fieldName, Object fieldValue) {
if (this.mapping().mappingValues().isEmpty() ||
!this.mapping().mappingValues().containsKey(fieldName)) {
return;
}
// NOTE: The nullable values has been filtered before this
E.checkArgument(fieldValue != null, "The field value can't be null");
E.checkArgument(DataTypeUtil.isSimpleValue(fieldValue),
"The field value must be simple type, actual is '%s'",
fieldValue.getClass());
}
private boolean vertexIdEmpty(VertexLabel vertexLabel, Vertex vertex) {
IdStrategy idStrategy = vertexLabel.idStrategy();
if (idStrategy.isCustomizeString()) {
Object vertexId = vertex.id();
return vertexId == null || StringUtils.isEmpty((String) vertexId);
}
return false;
}
private void checkPrimaryValuesValid(VertexLabel vertexLabel,
Object[] primaryValues) {
List<String> primaryKeys = vertexLabel.primaryKeys();
E.checkArgument(primaryKeys.size() == primaryValues.length,
"Missing some primary key values, expect %s, " +
"but only got %s for vertex label '%s'",
primaryKeys, Arrays.toString(primaryValues),
vertexLabel);
for (int i = 0; i < primaryKeys.size(); i++) {
E.checkArgument(primaryValues[i] != null,
"Make sure the value of the primary key '%s' is " +
"not empty, or check whether the headers or " +
"field_mapping are configured correctly",
primaryKeys.get(i));
}
}
private String spliceVertexId(VertexLabel vertexLabel,
Object... primaryValues) {
StringBuilder vertexId = new StringBuilder();
StringBuilder vertexKeysId = new StringBuilder();
for (int i = 0; i < primaryValues.length; i++) {
Object value = primaryValues[i];
String pkValue;
if (value instanceof Number || value instanceof Date) {
pkValue = LongEncoding.encodeNumber(value);
} else {
pkValue = String.valueOf(value);
}
if (StringUtils.containsAny(pkValue, Constants.SEARCH_LIST)) {
pkValue = StringUtils.replaceEach(pkValue,
Constants.SEARCH_LIST,
Constants.TARGET_LIST);
}
vertexKeysId.append(pkValue);
vertexKeysId.append("!");
}
vertexId.append(vertexLabel.id()).append(":").append(vertexKeysId);
vertexId.deleteCharAt(vertexId.length() - 1);
return vertexId.toString();
}
private void checkVertexIdLength(String id) {
this.encoder.reset();
this.buffer.clear();
CoderResult r = this.encoder.encode(CharBuffer.wrap(id.toCharArray()),
this.buffer, true);
E.checkArgument(r.isUnderflow(),
"The vertex id length exceeds limit %s : '%s'",
Constants.VERTEX_ID_LIMIT, id);
}
private boolean isEmptyPkValue(Object pkValue) {
if (pkValue == null) {
return true;
}
if (pkValue instanceof String) {
String pkValueStr = (String) pkValue;
return pkValueStr.isEmpty();
}
return false;
}
public abstract class VertexKVPairs {
public final VertexLabel vertexLabel;
// General properties
public Map<String, Object> properties;
public VertexKVPairs(VertexLabel vertexLabel) {
this.vertexLabel = vertexLabel;
this.properties = null;
}
public abstract void extractFromVertex(String[] names,
Object[] values);
public abstract void extractFromEdge(String[] names, Object[] values,
int[] fieldIndexes);
public abstract List<Vertex> buildVertices(boolean withProperty);
public List<Object> splitField(String key, Object value) {
return DataTypeUtil.splitField(key, value, struct.input());
}
}
public class VertexIdKVPairs extends VertexKVPairs {
// The idField(raw field), like: id
private String idField;
// The single idValue(mapped), like: A -> 1
private Object idValue;
public VertexIdKVPairs(VertexLabel vertexLabel) {
super(vertexLabel);
}
@Override
public void extractFromVertex(String[] names, Object[] values) {
// General properties
this.properties = new HashMap<>();
for (int i = 0; i < names.length; i++) {
String fieldName = names[i];
Object fieldValue = values[i];
if (!retainField(fieldName, fieldValue)) {
continue;
}
if (isIdField(fieldName)) {
this.idField = fieldName;
this.idValue = mappingValue(fieldName, fieldValue);
} else {
String key = mapping().mappingField(fieldName);
Object value = mappingValue(fieldName, fieldValue);
this.properties.put(key, value);
}
}
}
@Override
public void extractFromEdge(String[] names, Object[] values,
int[] fieldIndexes) {
assert fieldIndexes.length == 1;
String fieldName = names[fieldIndexes[0]];
Object fieldValue = values[fieldIndexes[0]];
this.idField = fieldName;
this.idValue = mappingValue(fieldName, fieldValue);
}
@Override
public List<Vertex> buildVertices(boolean withProperty) {
Vertex vertex = new Vertex(vertexLabel.name());
customizeId(vertexLabel, vertex, this.idField, this.idValue);
if (vertexIdEmpty(vertexLabel, vertex)) {
return ImmutableList.of();
}
if (withProperty) {
String key = mapping().mappingField(this.idField);
// The id field is also used as a general property
if (vertexLabel.properties().contains(key)) {
addProperty(vertex, key, this.idValue);
}
addProperties(vertex, this.properties);
checkNonNullableKeys(vertex);
}
return ImmutableList.of(vertex);
}
}
public class VertexFlatIdKVPairs extends VertexKVPairs {
// The idField(raw field), like: id
private String idField;
/*
* The multiple idValues(spilted and mapped)
* like: A|B|C -> [1,2,3]
*/
private List<Object> idValues;
public VertexFlatIdKVPairs(VertexLabel vertexLabel) {
super(vertexLabel);
}
@Override
public void extractFromVertex(String[] names, Object[] values) {
// General properties
this.properties = new HashMap<>();
for (int i = 0; i < names.length; i++) {
String fieldName = names[i];
Object fieldValue = values[i];
if (!retainField(fieldName, fieldValue)) {
continue;
}
if (isIdField(fieldName)) {
this.idField = fieldName;
List<Object> rawIdValues = splitField(fieldName,
fieldValue);
this.idValues = rawIdValues.stream().map(rawIdValue -> {
return mappingValue(fieldName, rawIdValue);
}).collect(Collectors.toList());
} else {
String key = mapping().mappingField(fieldName);
Object value = mappingValue(fieldName, fieldValue);
this.properties.put(key, value);
}
}
}
@Override
public void extractFromEdge(String[] names, Object[] values,
int[] fieldIndexes) {
assert fieldIndexes.length == 1;
String fieldName = names[fieldIndexes[0]];
Object fieldValue = values[fieldIndexes[0]];
this.idField = fieldName;
List<Object> rawIdValues = splitField(fieldName, fieldValue);
this.idValues = rawIdValues.stream().map(rawIdValue -> {
return mappingValue(fieldName, rawIdValue);
}).collect(Collectors.toList());
}
@Override
public List<Vertex> buildVertices(boolean withProperty) {
List<Vertex> vertices = new ArrayList<>(this.idValues.size());
for (Object idValue : this.idValues) {
Vertex vertex = new Vertex(vertexLabel.name());
customizeId(vertexLabel, vertex, this.idField, idValue);
if (vertexIdEmpty(vertexLabel, vertex)) {
continue;
}
if (withProperty) {
String key = mapping().mappingField(this.idField);
// The id field is also used as a general property
if (vertexLabel.properties().contains(key)) {
addProperty(vertex, key, idValue);
}
addProperties(vertex, this.properties);
checkNonNullableKeys(vertex);
}
vertices.add(vertex);
}
return vertices;
}
}
public class VertexPkKVPairs extends VertexKVPairs {
/*
* The primary key names(mapped), allowed multiple
* like: [p_name,p_age] -> [name,age]
*/
private List<String> pkNames;
/*
* The primary values(mapped), length is the same as pkNames
* like: [m,2] -> [marko,18]
*/
private Object[] pkValues;
public VertexPkKVPairs(VertexLabel vertexLabel) {
super(vertexLabel);
}
@Override
public void extractFromVertex(String[] names, Object[] values) {
List<String> primaryKeys = this.vertexLabel.primaryKeys();
this.pkNames = primaryKeys;
this.pkValues = new Object[primaryKeys.size()];
// General properties
this.properties = new HashMap<>();
for (int i = 0; i < names.length; i++) {
String fieldName = names[i];
Object fieldValue = values[i];
if (!retainField(fieldName, fieldValue)) {
continue;
}
String key = mapping().mappingField(fieldName);
if (primaryKeys.contains(key)) {
// Don't put priamry key/values into general properties
int index = primaryKeys.indexOf(key);
Object pkValue = mappingValue(fieldName, fieldValue);
this.pkValues[index] = pkValue;
} else {
Object value = mappingValue(fieldName, fieldValue);
this.properties.put(key, value);
}
}
}
@Override
public void extractFromEdge(String[] names, Object[] values,
int[] fieldIndexes) {
this.pkNames = new ArrayList<>(fieldIndexes.length);
for (int fieldIndex : fieldIndexes) {
String fieldName = names[fieldIndex];
String mappingField = mapping().mappingField(fieldName);
this.pkNames.add(mappingField);
}
List<String> primaryKeys = this.vertexLabel.primaryKeys();
E.checkArgument(ListUtils.isEqualList(this.pkNames, primaryKeys),
"Make sure the the primary key fields %s are " +
"not empty, or check whether the headers or " +
"field_mapping are configured correctly",
primaryKeys);
this.pkValues = new Object[this.pkNames.size()];
for (int i = 0; i < fieldIndexes.length; i++) {
String fieldName = names[fieldIndexes[i]];
Object fieldValue = values[fieldIndexes[i]];
Object pkValue = mappingValue(fieldName, fieldValue);
this.pkValues[i] = pkValue;
}
}
@Override
public List<Vertex> buildVertices(boolean withProperty) {
checkPrimaryValuesValid(vertexLabel, this.pkValues);
for (int i = 0; i < this.pkNames.size(); i++) {
if (isEmptyPkValue(this.pkValues[i])) {
return ImmutableList.of();
}
Object pkValue = convertPropertyValue(this.pkNames.get(i),
this.pkValues[i]);
this.pkValues[i] = pkValue;
}
String id = spliceVertexId(vertexLabel, this.pkValues);
checkVertexIdLength(id);
Vertex vertex = new Vertex(vertexLabel.name());
// NOTE: withProperty is true means that parsing vertex
if (withProperty) {
for (int i = 0; i < this.pkNames.size(); i++) {
addProperty(vertex, this.pkNames.get(i),
this.pkValues[i], false);
}
addProperties(vertex, this.properties);
checkNonNullableKeys(vertex);
}
vertex.id(id);
return ImmutableList.of(vertex);
}
}
public class VertexFlatPkKVPairs extends VertexKVPairs {
/*
* The primary key name(mapped), must be single
* like: p_name -> name
*/
private String pkName;
/*
* The primary values(splited and mapped)
* like: m|v -> [marko,vadas]
*/
private List<Object> pkValues;
public VertexFlatPkKVPairs(VertexLabel vertexLabel) {
super(vertexLabel);
}
@Override
public void extractFromVertex(String[] names, Object[] values) {
List<String> primaryKeys = vertexLabel.primaryKeys();
E.checkArgument(primaryKeys.size() == 1,
"In case unfold is true, just supported " +
"a single primary key");
this.pkName = primaryKeys.get(0);
// General properties
this.properties = new HashMap<>();
boolean handledPk = false;
for (int i = 0; i < names.length; i++) {
String fieldName = names[i];
Object fieldValue = values[i];
if (!retainField(fieldName, fieldValue)) {
continue;
}
String key = mapping().mappingField(fieldName);
if (!handledPk && primaryKeys.contains(key)) {
// Don't put priamry key/values into general properties
List<Object> rawPkValues = splitField(fieldName,
fieldValue);
this.pkValues = rawPkValues.stream().map(rawPkValue -> {
return mappingValue(fieldName, rawPkValue);
}).collect(Collectors.toList());
handledPk = true;
} else {
Object value = mappingValue(fieldName, fieldValue);
this.properties.put(key, value);
}
}
}
@Override
public void extractFromEdge(String[] names, Object[] values,
int[] fieldIndexes) {
List<String> primaryKeys = vertexLabel.primaryKeys();
E.checkArgument(fieldIndexes.length == 1 && primaryKeys.size() == 1,
"In case unfold is true, just supported " +
"a single primary key");
String fieldName = names[fieldIndexes[0]];
this.pkName = mapping().mappingField(fieldName);
String primaryKey = primaryKeys.get(0);
E.checkArgument(this.pkName.equals(primaryKey),
"Make sure the the primary key field '%s' is " +
"not empty, or check whether the headers or " +
"field_mapping are configured correctly",
primaryKey);
Object fieldValue = values[fieldIndexes[0]];
List<Object> rawPkValues = splitField(fieldName, fieldValue);
this.pkValues = rawPkValues.stream().map(rawPkValue -> {
return mappingValue(fieldName, rawPkValue);
}).collect(Collectors.toList());
}
@Override
public List<Vertex> buildVertices(boolean withProperty) {
E.checkArgument(this.pkValues != null,
"The primary values shouldn't be null");
List<Vertex> vertices = new ArrayList<>(this.pkValues.size());
for (Object pkValue : this.pkValues) {
if (isEmptyPkValue(pkValue)) {
continue;
}
pkValue = convertPropertyValue(this.pkName, pkValue);
String id = spliceVertexId(vertexLabel, pkValue);
checkVertexIdLength(id);
Vertex vertex = new Vertex(vertexLabel.name());
// NOTE: withProperty is true means that parsing vertex
if (withProperty) {
addProperty(vertex, this.pkName, pkValue, false);
addProperties(vertex, this.properties);
checkNonNullableKeys(vertex);
}
vertex.id(id);
vertices.add(vertex);
}
return vertices;
}
}
}